Loading android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java +85 −3 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.provider.CallLog; import android.sysprop.BluetoothProperties; import android.util.Log; Loading Loading @@ -60,6 +61,12 @@ public class PbapClientService extends ProfileService { private static final String TAG = "PbapClientService"; private static final String SERVICE_NAME = "Phonebook Access PCE"; /** * The component names for the owned authenticator service */ private static final String AUTHENTICATOR_SERVICE = AuthenticationService.class.getCanonicalName(); // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. private static final int MAXIMUM_DEVICES = 10; private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = Loading @@ -70,6 +77,40 @@ public class PbapClientService extends ProfileService { private DatabaseManager mDatabaseManager; /** * There's an ~1-2 second latency between when our Authentication service is set as available to * the system and when the Authentication/Account framework code will recognize it and allow us * to alter accounts. In lieu of the Accounts team dealing with this race condition, we're going * to periodically poll over 3 seconds until our accounts are visible, remove old accounts, and * then notify device state machines that they can create accounts and download contacts. */ // TODO(233361365): Remove this pattern when the framework solves their race condition private static final int ACCOUNT_VISIBILITY_CHECK_MS = 500; private static final int ACCOUNT_VISIBILITY_CHECK_TRIES_MAX = 6; private int mAccountVisibilityCheckTries = 0; private final Handler mAuthServiceHandler = new Handler(); private final Runnable mCheckAuthService = new Runnable() { @Override public void run() { // If our accounts are finally visible to use, clean up old ones and tell devices they // can issue downloads if they're ready. Otherwise, wait and try again. if (isAuthenticationServiceReady()) { Log.i(TAG, "Service ready! Clean up old accounts and try contacts downloads"); removeUncleanAccounts(); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.tryDownloadIfConnected(); } } else if (mAccountVisibilityCheckTries < ACCOUNT_VISIBILITY_CHECK_TRIES_MAX) { mAccountVisibilityCheckTries += 1; Log.w(TAG, "AccountManager hasn't registered our service yet. Retry " + mAccountVisibilityCheckTries + "/" + ACCOUNT_VISIBILITY_CHECK_TRIES_MAX); mAuthServiceHandler.postDelayed(this, ACCOUNT_VISIBILITY_CHECK_MS); } else { Log.e(TAG, "Failed to register Authenication Service and get account visibility"); } } }; public static boolean isEnabled() { return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); } Loading Loading @@ -101,7 +142,7 @@ public class PbapClientService extends ProfileService { Log.w(TAG, "Unable to register pbapclient receiver", e); } removeUncleanAccounts(); initializeAuthenticationService(); registerSdpRecord(); setPbapClientService(this); return true; Loading @@ -119,7 +160,9 @@ public class PbapClientService extends ProfileService { for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { pbapClientStateMachine.doQuit(); } removeUncleanAccounts(); cleanupAuthenicationService(); setComponentAvailable(AUTHENTICATOR_SERVICE, false); return true; } Loading @@ -133,7 +176,45 @@ public class PbapClientService extends ProfileService { } } /** * Periodically check if the account framework has recognized our service and will allow us to * interact with our accounts. Notify state machines once our service is ready so we can trigger * account downloads. */ private void initializeAuthenticationService() { mAuthServiceHandler.postDelayed(mCheckAuthService, ACCOUNT_VISIBILITY_CHECK_MS); } private void cleanupAuthenicationService() { mAuthServiceHandler.removeCallbacks(mCheckAuthService); removeUncleanAccounts(); } /** * Determine if our account type is visible to us yet. If it is, then our service is ready and * our account type is ready to use. * * Make a placeholder device account and determine our visibility relative to it. Note that this * function uses the same restrictions are the other add and remove functions, but is *also* * available to all system apps instead of throwing a runtime SecurityException. */ protected boolean isAuthenticationServiceReady() { Account account = new Account("00:00:00:00:00:00", getString(R.string.pbap_account_type)); AccountManager accountManager = AccountManager.get(this); int visibility = accountManager.getAccountVisibility(account, getPackageName()); if (DBG) { Log.d(TAG, "Checking visibility, visibility=" + visibility); } return visibility == AccountManager.VISIBILITY_VISIBLE || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; } private void removeUncleanAccounts() { if (!isAuthenticationServiceReady()) { Log.w(TAG, "Can't remove accounts. AccountManager hasn't registered our service yet."); return; } // Find all accounts that match the type "pbap" and delete them. AccountManager accountManager = AccountManager.get(this); Account[] accounts = Loading Loading @@ -208,7 +289,7 @@ public class PbapClientService extends ProfileService { } } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.resumeDownload(); stateMachine.tryDownloadIfConnected(); } } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. Loading Loading @@ -542,6 +623,7 @@ public class PbapClientService extends ProfileService { @Override public void dump(StringBuilder sb) { super.dump(sb); ProfileService.println(sb, "isAuthServiceReady: " + isAuthenticationServiceReady()); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.dump(sb); } Loading android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java +18 −7 Original line number Diff line number Diff line Loading @@ -301,10 +301,7 @@ final class PbapClientStateMachine extends StateMachine { onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_CONNECTED); mMostRecentState = BluetoothProfile.STATE_CONNECTED; if (mUserManager.isUserUnlocked()) { mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } downloadIfReady(); } @Override Loading @@ -321,8 +318,7 @@ final class PbapClientStateMachine extends StateMachine { break; case MSG_RESUME_DOWNLOAD: mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); downloadIfReady(); break; default: Loading @@ -333,6 +329,21 @@ final class PbapClientStateMachine extends StateMachine { } } /** * Trigger a contacts download if the user is unlocked and our accounts are available to us */ private void downloadIfReady() { boolean userReady = mUserManager.isUserUnlocked(); boolean accountServiceReady = mService.isAuthenticationServiceReady(); if (!userReady || !accountServiceReady) { Log.w(TAG, "Cannot download contacts yet, userReady=" + userReady + ", accountServiceReady=" + accountServiceReady); return; } mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { if (device == null) { Log.w(TAG, "onConnectionStateChanged with invalid device"); Loading @@ -357,7 +368,7 @@ final class PbapClientStateMachine extends StateMachine { sendMessage(MSG_DISCONNECT, device); } public void resumeDownload() { public void tryDownloadIfConnected() { sendMessage(MSG_RESUME_DOWNLOAD); } Loading Loading
android/app/src/com/android/bluetooth/pbapclient/PbapClientService.java +85 −3 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.provider.CallLog; import android.sysprop.BluetoothProperties; import android.util.Log; Loading Loading @@ -60,6 +61,12 @@ public class PbapClientService extends ProfileService { private static final String TAG = "PbapClientService"; private static final String SERVICE_NAME = "Phonebook Access PCE"; /** * The component names for the owned authenticator service */ private static final String AUTHENTICATOR_SERVICE = AuthenticationService.class.getCanonicalName(); // MAXIMUM_DEVICES set to 10 to prevent an excessive number of simultaneous devices. private static final int MAXIMUM_DEVICES = 10; private Map<BluetoothDevice, PbapClientStateMachine> mPbapClientStateMachineMap = Loading @@ -70,6 +77,40 @@ public class PbapClientService extends ProfileService { private DatabaseManager mDatabaseManager; /** * There's an ~1-2 second latency between when our Authentication service is set as available to * the system and when the Authentication/Account framework code will recognize it and allow us * to alter accounts. In lieu of the Accounts team dealing with this race condition, we're going * to periodically poll over 3 seconds until our accounts are visible, remove old accounts, and * then notify device state machines that they can create accounts and download contacts. */ // TODO(233361365): Remove this pattern when the framework solves their race condition private static final int ACCOUNT_VISIBILITY_CHECK_MS = 500; private static final int ACCOUNT_VISIBILITY_CHECK_TRIES_MAX = 6; private int mAccountVisibilityCheckTries = 0; private final Handler mAuthServiceHandler = new Handler(); private final Runnable mCheckAuthService = new Runnable() { @Override public void run() { // If our accounts are finally visible to use, clean up old ones and tell devices they // can issue downloads if they're ready. Otherwise, wait and try again. if (isAuthenticationServiceReady()) { Log.i(TAG, "Service ready! Clean up old accounts and try contacts downloads"); removeUncleanAccounts(); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.tryDownloadIfConnected(); } } else if (mAccountVisibilityCheckTries < ACCOUNT_VISIBILITY_CHECK_TRIES_MAX) { mAccountVisibilityCheckTries += 1; Log.w(TAG, "AccountManager hasn't registered our service yet. Retry " + mAccountVisibilityCheckTries + "/" + ACCOUNT_VISIBILITY_CHECK_TRIES_MAX); mAuthServiceHandler.postDelayed(this, ACCOUNT_VISIBILITY_CHECK_MS); } else { Log.e(TAG, "Failed to register Authenication Service and get account visibility"); } } }; public static boolean isEnabled() { return BluetoothProperties.isProfilePbapClientEnabled().orElse(false); } Loading Loading @@ -101,7 +142,7 @@ public class PbapClientService extends ProfileService { Log.w(TAG, "Unable to register pbapclient receiver", e); } removeUncleanAccounts(); initializeAuthenticationService(); registerSdpRecord(); setPbapClientService(this); return true; Loading @@ -119,7 +160,9 @@ public class PbapClientService extends ProfileService { for (PbapClientStateMachine pbapClientStateMachine : mPbapClientStateMachineMap.values()) { pbapClientStateMachine.doQuit(); } removeUncleanAccounts(); cleanupAuthenicationService(); setComponentAvailable(AUTHENTICATOR_SERVICE, false); return true; } Loading @@ -133,7 +176,45 @@ public class PbapClientService extends ProfileService { } } /** * Periodically check if the account framework has recognized our service and will allow us to * interact with our accounts. Notify state machines once our service is ready so we can trigger * account downloads. */ private void initializeAuthenticationService() { mAuthServiceHandler.postDelayed(mCheckAuthService, ACCOUNT_VISIBILITY_CHECK_MS); } private void cleanupAuthenicationService() { mAuthServiceHandler.removeCallbacks(mCheckAuthService); removeUncleanAccounts(); } /** * Determine if our account type is visible to us yet. If it is, then our service is ready and * our account type is ready to use. * * Make a placeholder device account and determine our visibility relative to it. Note that this * function uses the same restrictions are the other add and remove functions, but is *also* * available to all system apps instead of throwing a runtime SecurityException. */ protected boolean isAuthenticationServiceReady() { Account account = new Account("00:00:00:00:00:00", getString(R.string.pbap_account_type)); AccountManager accountManager = AccountManager.get(this); int visibility = accountManager.getAccountVisibility(account, getPackageName()); if (DBG) { Log.d(TAG, "Checking visibility, visibility=" + visibility); } return visibility == AccountManager.VISIBILITY_VISIBLE || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE; } private void removeUncleanAccounts() { if (!isAuthenticationServiceReady()) { Log.w(TAG, "Can't remove accounts. AccountManager hasn't registered our service yet."); return; } // Find all accounts that match the type "pbap" and delete them. AccountManager accountManager = AccountManager.get(this); Account[] accounts = Loading Loading @@ -208,7 +289,7 @@ public class PbapClientService extends ProfileService { } } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) { for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.resumeDownload(); stateMachine.tryDownloadIfConnected(); } } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) { // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects. Loading Loading @@ -542,6 +623,7 @@ public class PbapClientService extends ProfileService { @Override public void dump(StringBuilder sb) { super.dump(sb); ProfileService.println(sb, "isAuthServiceReady: " + isAuthenticationServiceReady()); for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) { stateMachine.dump(sb); } Loading
android/app/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java +18 −7 Original line number Diff line number Diff line Loading @@ -301,10 +301,7 @@ final class PbapClientStateMachine extends StateMachine { onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_CONNECTED); mMostRecentState = BluetoothProfile.STATE_CONNECTED; if (mUserManager.isUserUnlocked()) { mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } downloadIfReady(); } @Override Loading @@ -321,8 +318,7 @@ final class PbapClientStateMachine extends StateMachine { break; case MSG_RESUME_DOWNLOAD: mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); downloadIfReady(); break; default: Loading @@ -333,6 +329,21 @@ final class PbapClientStateMachine extends StateMachine { } } /** * Trigger a contacts download if the user is unlocked and our accounts are available to us */ private void downloadIfReady() { boolean userReady = mUserManager.isUserUnlocked(); boolean accountServiceReady = mService.isAuthenticationServiceReady(); if (!userReady || !accountServiceReady) { Log.w(TAG, "Cannot download contacts yet, userReady=" + userReady + ", accountServiceReady=" + accountServiceReady); return; } mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) .sendToTarget(); } private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) { if (device == null) { Log.w(TAG, "onConnectionStateChanged with invalid device"); Loading @@ -357,7 +368,7 @@ final class PbapClientStateMachine extends StateMachine { sendMessage(MSG_DISCONNECT, device); } public void resumeDownload() { public void tryDownloadIfConnected() { sendMessage(MSG_RESUME_DOWNLOAD); } Loading