Loading android/app/src/com/android/bluetooth/hfpclient/connserv/ConnectionKey.java 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.hfpclient.connserv; import android.bluetooth.BluetoothHeadsetClientCall; import android.net.Uri; import android.telecom.PhoneAccount; /* Matching a call to internal state requires understanding of the ph number and the state of the * remote itself. The best way to associate a call with remote is to use the Call IDs that are * passed by the HFP AG role. But consider the scenario when even before a call is notified to the * remote and it tries to get back to us -- we execute a full cycle of Call -> Hangup. In such case * we have no recourse but to use phone number as the key. This class implements the matching logic * for phone numbers & IDs. It identifies uniquely a {@link HfpClientConnection}. */ class ConnectionKey { /* Initialize with invalid values */ public static final int INVALID_ID = -1; private final int mId; private final Uri mPhoneNumber; ConnectionKey(int id, Uri phoneNumber) { if (id == INVALID_ID && phoneNumber == null) { throw new IllegalStateException("invalid id and phone number"); } mId = id; mPhoneNumber = phoneNumber; } public static ConnectionKey getKey(BluetoothHeadsetClientCall call) { if (call == null) { throw new IllegalStateException("call may not be null"); } // IDs in the call start with *1*. int id = call.getId() > 0 ? call.getId() : INVALID_ID; Uri phoneNumberUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); return new ConnectionKey(id, phoneNumberUri); } public int getId() { return mId; } public Uri getPhoneNumber() { return mPhoneNumber; } @Override public String toString() { return "Key " + getId() + " Phone number " + getPhoneNumber(); } @Override public boolean equals(Object o) { if (!(o instanceof ConnectionKey)) { return false; } ConnectionKey candidate = (ConnectionKey) o; /* Match based on IDs */ if (getId() != INVALID_ID && candidate.getId() != INVALID_ID) { return (getId() == candidate.getId()); } /* Otherwise match has to be based on phone numbers */ return (getPhoneNumber() != null && candidate.getPhoneNumber() != null && getPhoneNumber().equals(candidate.getPhoneNumber())); } @Override public int hashCode() { // Since we may do partial match based on either ID or phone numbers hence put all the items // in same bucket. return 0; } } android/app/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java +37 −10 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import java.util.Map; public class HfpClientConnectionService extends ConnectionService { private static final String TAG = "HfpClientConnService"; private static final boolean DBG = true; public static final String HFP_SCHEME = "hfpc"; Loading @@ -59,7 +60,8 @@ public class HfpClientConnectionService extends ConnectionService { private BluetoothHeadsetClient mHeadsetProfile; private TelecomManager mTelecomManager; private Map<Uri, HfpClientConnection> mConnections = new HashMap<>(); private Map<ConnectionKey, HfpClientConnection> mConnections = new HashMap<ConnectionKey, HfpClientConnection>(); private HfpClientConference mConference; private boolean mPendingAcceptCall; Loading Loading @@ -195,11 +197,27 @@ public class HfpClientConnectionService extends ConnectionService { } } private void handleCall(BluetoothHeadsetClientCall call) { Log.d(TAG, "Got call " + call); // Find the connection specified by the key, also update the key with ID if present. private synchronized HfpClientConnection findConnectionAndUpdateKey(ConnectionKey key) { if (DBG) { Log.d(TAG, "findConnectionAndUpdateKey local key set " + mConnections.toString()); } HfpClientConnection conn = mConnections.get(key); if (conn != null && key.getId() != ConnectionKey.INVALID_ID) { Log.d(TAG, "Updating key for " + key.getPhoneNumber() + " to " + key.getId()); mConnections.remove(key); mConnections.put(key, conn); } return conn; } private void handleCall(BluetoothHeadsetClientCall call) { Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); HfpClientConnection connection = mConnections.get(number); Log.d(TAG, "Got call " + call.toString(true) + "/" + number); ConnectionKey incomingKey = ConnectionKey.getKey(call); HfpClientConnection connection = findConnectionAndUpdateKey(incomingKey); if (connection != null) { connection.handleCallChanged(call); } Loading Loading @@ -230,7 +248,9 @@ public class HfpClientConnectionService extends ConnectionService { } } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) { Log.d(TAG, "Removing number " + number); mConnections.remove(number); synchronized (this) { mConnections.remove(ConnectionKey.getKey(call)); } } updateConferenceableConnections(); } Loading @@ -251,8 +271,11 @@ public class HfpClientConnectionService extends ConnectionService { BluetoothHeadsetClientCall call = request.getExtras().getParcelable( TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); HfpClientConnection connection = mConnections.get(number); HfpClientConnection connection = null; synchronized (this) { connection = mConnections.get(ConnectionKey.getKey(call)); } if (connection != null) { connection.onAdded(); Loading Loading @@ -302,7 +325,11 @@ public class HfpClientConnectionService extends ConnectionService { request.getExtras().getParcelable( TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); HfpClientConnection connection = mConnections.get(number); HfpClientConnection connection = null; synchronized (this) { connection = mConnections.get(ConnectionKey.getKey(call)); } if (connection != null) { connection.onAdded(); Loading Loading @@ -419,12 +446,12 @@ public class HfpClientConnectionService extends ConnectionService { return mAdapter.getRemoteDevice(btAddr); } private HfpClientConnection buildConnection( private synchronized HfpClientConnection buildConnection( BluetoothDevice device, BluetoothHeadsetClientCall call, Uri number) { Log.d(TAG, "Creating connection on " + device + " for " + call + "/" + number); HfpClientConnection connection = new HfpClientConnection(this, device, mHeadsetProfile, call, number); mConnections.put(number, connection); mConnections.put(new ConnectionKey(ConnectionKey.INVALID_ID, number), connection); return connection; } Loading Loading
android/app/src/com/android/bluetooth/hfpclient/connserv/ConnectionKey.java 0 → 100644 +92 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.hfpclient.connserv; import android.bluetooth.BluetoothHeadsetClientCall; import android.net.Uri; import android.telecom.PhoneAccount; /* Matching a call to internal state requires understanding of the ph number and the state of the * remote itself. The best way to associate a call with remote is to use the Call IDs that are * passed by the HFP AG role. But consider the scenario when even before a call is notified to the * remote and it tries to get back to us -- we execute a full cycle of Call -> Hangup. In such case * we have no recourse but to use phone number as the key. This class implements the matching logic * for phone numbers & IDs. It identifies uniquely a {@link HfpClientConnection}. */ class ConnectionKey { /* Initialize with invalid values */ public static final int INVALID_ID = -1; private final int mId; private final Uri mPhoneNumber; ConnectionKey(int id, Uri phoneNumber) { if (id == INVALID_ID && phoneNumber == null) { throw new IllegalStateException("invalid id and phone number"); } mId = id; mPhoneNumber = phoneNumber; } public static ConnectionKey getKey(BluetoothHeadsetClientCall call) { if (call == null) { throw new IllegalStateException("call may not be null"); } // IDs in the call start with *1*. int id = call.getId() > 0 ? call.getId() : INVALID_ID; Uri phoneNumberUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); return new ConnectionKey(id, phoneNumberUri); } public int getId() { return mId; } public Uri getPhoneNumber() { return mPhoneNumber; } @Override public String toString() { return "Key " + getId() + " Phone number " + getPhoneNumber(); } @Override public boolean equals(Object o) { if (!(o instanceof ConnectionKey)) { return false; } ConnectionKey candidate = (ConnectionKey) o; /* Match based on IDs */ if (getId() != INVALID_ID && candidate.getId() != INVALID_ID) { return (getId() == candidate.getId()); } /* Otherwise match has to be based on phone numbers */ return (getPhoneNumber() != null && candidate.getPhoneNumber() != null && getPhoneNumber().equals(candidate.getPhoneNumber())); } @Override public int hashCode() { // Since we may do partial match based on either ID or phone numbers hence put all the items // in same bucket. return 0; } }
android/app/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionService.java +37 −10 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import java.util.Map; public class HfpClientConnectionService extends ConnectionService { private static final String TAG = "HfpClientConnService"; private static final boolean DBG = true; public static final String HFP_SCHEME = "hfpc"; Loading @@ -59,7 +60,8 @@ public class HfpClientConnectionService extends ConnectionService { private BluetoothHeadsetClient mHeadsetProfile; private TelecomManager mTelecomManager; private Map<Uri, HfpClientConnection> mConnections = new HashMap<>(); private Map<ConnectionKey, HfpClientConnection> mConnections = new HashMap<ConnectionKey, HfpClientConnection>(); private HfpClientConference mConference; private boolean mPendingAcceptCall; Loading Loading @@ -195,11 +197,27 @@ public class HfpClientConnectionService extends ConnectionService { } } private void handleCall(BluetoothHeadsetClientCall call) { Log.d(TAG, "Got call " + call); // Find the connection specified by the key, also update the key with ID if present. private synchronized HfpClientConnection findConnectionAndUpdateKey(ConnectionKey key) { if (DBG) { Log.d(TAG, "findConnectionAndUpdateKey local key set " + mConnections.toString()); } HfpClientConnection conn = mConnections.get(key); if (conn != null && key.getId() != ConnectionKey.INVALID_ID) { Log.d(TAG, "Updating key for " + key.getPhoneNumber() + " to " + key.getId()); mConnections.remove(key); mConnections.put(key, conn); } return conn; } private void handleCall(BluetoothHeadsetClientCall call) { Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); HfpClientConnection connection = mConnections.get(number); Log.d(TAG, "Got call " + call.toString(true) + "/" + number); ConnectionKey incomingKey = ConnectionKey.getKey(call); HfpClientConnection connection = findConnectionAndUpdateKey(incomingKey); if (connection != null) { connection.handleCallChanged(call); } Loading Loading @@ -230,7 +248,9 @@ public class HfpClientConnectionService extends ConnectionService { } } else if (call.getState() == BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) { Log.d(TAG, "Removing number " + number); mConnections.remove(number); synchronized (this) { mConnections.remove(ConnectionKey.getKey(call)); } } updateConferenceableConnections(); } Loading @@ -251,8 +271,11 @@ public class HfpClientConnectionService extends ConnectionService { BluetoothHeadsetClientCall call = request.getExtras().getParcelable( TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); HfpClientConnection connection = mConnections.get(number); HfpClientConnection connection = null; synchronized (this) { connection = mConnections.get(ConnectionKey.getKey(call)); } if (connection != null) { connection.onAdded(); Loading Loading @@ -302,7 +325,11 @@ public class HfpClientConnectionService extends ConnectionService { request.getExtras().getParcelable( TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, call.getNumber(), null); HfpClientConnection connection = mConnections.get(number); HfpClientConnection connection = null; synchronized (this) { connection = mConnections.get(ConnectionKey.getKey(call)); } if (connection != null) { connection.onAdded(); Loading Loading @@ -419,12 +446,12 @@ public class HfpClientConnectionService extends ConnectionService { return mAdapter.getRemoteDevice(btAddr); } private HfpClientConnection buildConnection( private synchronized HfpClientConnection buildConnection( BluetoothDevice device, BluetoothHeadsetClientCall call, Uri number) { Log.d(TAG, "Creating connection on " + device + " for " + call + "/" + number); HfpClientConnection connection = new HfpClientConnection(this, device, mHeadsetProfile, call, number); mConnections.put(number, connection); mConnections.put(new ConnectionKey(ConnectionKey.INVALID_ID, number), connection); return connection; } Loading