From 8bc55a6586b985f3f9d7a7c796a86fdc47c9b4b8 Mon Sep 17 00:00:00 2001 From: Joseph Pirozzo Date: Fri, 22 Apr 2016 09:20:30 -0700 Subject: [PATCH] PBAP Download Contacts in state machine Add refactored code to the new PBAP state machine to implement the safe downloading of contacts. Key improvements are no internal state handling, no complex message passing, and an improved ability to abort and handle error conditions. bug: 28249138 Change-Id: I30aa6ab6730d92e3ec797392ee9a1be7f55fa46a --- .../pbapclient/PbapClientConnectionHandler.java | 82 +++++++++++++++++++++- .../bluetooth/pbapclient/PbapClientService.java | 19 ++++- .../pbapclient/PbapClientStateMachine.java | 74 ++++++++++++++++--- 3 files changed, 159 insertions(+), 16 deletions(-) diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java index 135ec0f7..97dca7d6 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java @@ -15,17 +15,23 @@ */ package com.android.bluetooth.pbapclient; +import android.accounts.Account; +import android.accounts.AccountManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothUuid; +import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; +import android.provider.CallLog; import android.util.Log; +import com.android.bluetooth.R; + import java.io.IOException; import javax.obex.ClientSession; @@ -44,6 +50,8 @@ class PbapClientConnectionHandler extends Handler { static final int MSG_DISCONNECT = 2; static final int MSG_DOWNLOAD = 3; + // The following constants are pulled from the Bluetooth Phone Book Access Profile specification + // 1.1 private static final byte[] PBAP_TARGET = new byte[] { 0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66, 0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66 @@ -51,14 +59,19 @@ class PbapClientConnectionHandler extends Handler { public static final String PB_PATH = "telecom/pb.vcf"; public static final String MCH_PATH = "telecom/mch.vcf"; + public static final String ICH_PATH = "telecom/ich.vcf"; + public static final String OCH_PATH = "telecom/och.vcf"; public static final byte VCARD_TYPE_21 = 0; + private Account mAccount; + private AccountManager mAccountManager; private BluetoothSocket mSocket; private final BluetoothAdapter mAdapter; private final BluetoothDevice mDevice; private ClientSession mObexSession; private BluetoothPbapObexAuthenticator mAuth = null; private final PbapClientStateMachine mPbapClientStateMachine; + private boolean mAccountCreated; PbapClientConnectionHandler(Looper looper, PbapClientStateMachine stateMachine, BluetoothDevice device) { @@ -67,6 +80,7 @@ class PbapClientConnectionHandler extends Handler { mDevice = device; mPbapClientStateMachine = stateMachine; mAuth = new BluetoothPbapObexAuthenticator(this); + mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext()); } @Override @@ -80,6 +94,10 @@ class PbapClientConnectionHandler extends Handler { /* To establish a connection first open a socket, establish a OBEX Transport * abstraction, establish a Bluetooth Authenticator, and finally attempt to * connect via an OBEX session */ + mAccount = new Account(mDevice.getAddress(), + mPbapClientStateMachine.getContext() + .getString(R.string.pbap_account_type)); + mSocket = mDevice.createRfcommSocketToServiceRecord( BluetoothUuid.PBAP_PSE.getUuid()); mSocket.connect(); @@ -116,24 +134,45 @@ class PbapClientConnectionHandler extends Handler { if (mObexSession != null) { mObexSession.disconnect(null); } + closeSocket(); } catch (IOException e) { Log.w(TAG,"DISCONNECT Failure " + e.toString()); } + removeAccount(mAccount); + mPbapClientStateMachine.getContext().getContentResolver() + .delete(CallLog.Calls.CONTENT_URI, null, null); mPbapClientStateMachine.obtainMessage( PbapClientStateMachine.MSG_CONNECTION_CLOSED).sendToTarget(); break; case MSG_DOWNLOAD: + if (mAccountCreated == true) { + // If the account exists download has already completed, don't try again. + return; + } try { + mAccountCreated = addAccount(mAccount); + if (mAccountCreated == false) { + Log.d(TAG,"Account creation failed, normal durring startup"); + return; + } BluetoothPbapRequestPullPhoneBook request = - new BluetoothPbapRequestPullPhoneBook(PB_PATH, null, 0, VCARD_TYPE_21, - 0, 0); + new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, 0, + VCARD_TYPE_21, 0, 0); request.execute(mObexSession); - if (DBG) Log.d(TAG,"Download success? " + request.isSuccess()); + PhonebookPullRequest processor = + new PhonebookPullRequest(mPbapClientStateMachine.getContext(), mAccount); + processor.setResults(request.getList()); + processor.onPullComplete(); + + downloadCallLog(MCH_PATH); + downloadCallLog(ICH_PATH); + downloadCallLog(OCH_PATH); } catch (IOException e) { Log.w(TAG,"DOWNLOAD_CONTACTS Failure" + e.toString()); } break; + default: Log.w(TAG,"Received Unexpected Message"); } @@ -141,7 +180,10 @@ class PbapClientConnectionHandler extends Handler { } public void abort() { + // Perform forced cleanup, it is ok if the handler throws an exception this will free the + // handler to complete what it is doing and finish with cleanup. closeSocket(); + this.getLooper().getThread().interrupt(); } private void closeSocket() { @@ -152,6 +194,40 @@ class PbapClientConnectionHandler extends Handler { } } catch (IOException e) { Log.e(TAG, "Error when closing socket", e); + mSocket = null; + } + } + void downloadCallLog(String path) { + try { + BluetoothPbapRequestPullPhoneBook request = + new BluetoothPbapRequestPullPhoneBook(path,mAccount,0,VCARD_TYPE_21,0,0); + request.execute(mObexSession); + CallLogPullRequest processor = + new CallLogPullRequest(mPbapClientStateMachine.getContext(),path); + processor.setResults(request.getList()); + processor.onPullComplete(); + } catch (IOException e) { + Log.w(TAG,"Download call log failure"); + } + } + + private boolean addAccount(Account account) { + if (mAccountManager.addAccountExplicitly(account, null, null)) { + if (DBG) { + Log.d(TAG, "Added account " + mAccount); + } + return true; + } + return false; + } + + private void removeAccount(Account acc) { + if (mAccountManager.removeAccountExplicitly(acc)) { + if (DBG) { + Log.d(TAG, "Removed account " + acc); + } + } else { + Log.e(TAG, "Failed to remove account " + mAccount); } } } diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java index 3b67b15f..5c8ad8a5 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientService.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java @@ -74,6 +74,8 @@ public class PbapClientService extends ProfileService { protected boolean start() { IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + // delay initial download until after the user is unlocked to add an account. + filter.addAction(Intent.ACTION_USER_UNLOCKED); try { registerReceiver(mPbapBroadcastReceiver, filter); } catch (Exception e) { @@ -92,7 +94,9 @@ public class PbapClientService extends ProfileService { } catch (Exception e) { Log.w(TAG,"Unable to unregister pbapclient receiver",e); } - mPbapClientStateMachine.disconnect(null); + if (mPbapClientStateMachine != null) { + mPbapClientStateMachine.doQuit(); + } return true; } @@ -109,7 +113,11 @@ public class PbapClientService extends ProfileService { String action = intent.getAction(); if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - disconnect(device); + if (getConnectionState(device) != BluetoothProfile.STATE_DISCONNECTED) { + disconnect(device); + } + } else if(action.equals(Intent.ACTION_USER_UNLOCKED)) { + mPbapClientStateMachine.resumeDownload(); } } } @@ -307,4 +315,11 @@ public class PbapClientService extends ProfileService { return priority; } + @Override + public void dump(StringBuilder sb) { + super.dump(sb); + if (mPbapClientStateMachine != null) { + mPbapClientStateMachine.dump(sb); + } + } } diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java index e61ec1a6..27b96268 100644 --- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java +++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java @@ -41,6 +41,8 @@ */ package com.android.bluetooth.pbapclient; +import android.accounts.Account; +import android.accounts.AccountManager; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothPbapClient; @@ -49,9 +51,11 @@ import android.content.Intent; import android.os.Message; import android.os.Process; import android.os.HandlerThread; +import android.provider.CallLog; import android.util.Log; import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.R; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -76,6 +80,7 @@ final class PbapClientStateMachine extends StateMachine { static final int MSG_CONNECTION_COMPLETE = 5; static final int MSG_CONNECTION_FAILED = 6; static final int MSG_CONNECTION_CLOSED = 7; + static final int MSG_RESUME_DOWNLOAD = 8; static final int CONNECT_TIMEOUT = 6000; static final int DISCONNECT_TIMEOUT = 3000; @@ -142,6 +147,9 @@ final class PbapClientStateMachine extends StateMachine { break; case MSG_DISCONNECT: + Log.w(TAG,"Received unexpected disconnect while disconnected."); + // It is possible if something crashed for others to think we are connected + // already, just remind them. if (message.obj instanceof BluetoothDevice) { onConnectionStateChanged((BluetoothDevice) message.obj, BluetoothProfile.STATE_DISCONNECTED, @@ -149,6 +157,10 @@ final class PbapClientStateMachine extends StateMachine { } break; + case MSG_RESUME_DOWNLOAD: + // Do nothing. + break; + default: Log.w(TAG,"Received unexpected message while disconnected."); return NOT_HANDLED; @@ -158,15 +170,9 @@ final class PbapClientStateMachine extends StateMachine { } class Connecting extends State { - private boolean mAccountCreated; - private boolean mObexAuthorized; - @Override public void enter() { if (DBG) Log.d(TAG,"Enter Connecting: " + getCurrentMessage().what); - - mAccountCreated = false; - mObexAuthorized = false; onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_CONNECTING); mMostRecentState = BluetoothProfile.STATE_CONNECTING; @@ -180,7 +186,6 @@ final class PbapClientStateMachine extends StateMachine { mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_CONNECT) .sendToTarget(); sendMessageDelayed(MSG_CONNECT_TIMEOUT, CONNECT_TIMEOUT); - // TODO: create account } @Override @@ -206,6 +211,10 @@ final class PbapClientStateMachine extends StateMachine { Log.w(TAG,"Connecting already in progress"); break; + case MSG_RESUME_DOWNLOAD: + // Do nothing. + break; + default: Log.w(TAG,"Received unexpected message while Connecting"); return NOT_HANDLED; @@ -221,8 +230,9 @@ final class PbapClientStateMachine extends StateMachine { onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_DISCONNECTING); mMostRecentState = BluetoothProfile.STATE_DISCONNECTING; - mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DISCONNECT).sendToTarget(); - sendMessageDelayed(MSG_DISCONNECT_TIMEOUT, DISCONNECT_TIMEOUT); + mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DISCONNECT) + .sendToTarget(); + sendMessageDelayed(MSG_DISCONNECT_TIMEOUT,DISCONNECT_TIMEOUT); } @Override @@ -245,6 +255,10 @@ final class PbapClientStateMachine extends StateMachine { mConnectionHandler.abort(); break; + case MSG_RESUME_DOWNLOAD: + // Do nothing. + break; + default: Log.w(TAG,"Received unexpected message while Disconnecting"); return NOT_HANDLED; @@ -260,8 +274,8 @@ final class PbapClientStateMachine extends StateMachine { onConnectionStateChanged(mCurrentDevice, mMostRecentState, BluetoothProfile.STATE_CONNECTED); mMostRecentState = BluetoothProfile.STATE_CONNECTED; - // mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) - // .sendToTarget(); + mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) + .sendToTarget(); } @Override @@ -280,6 +294,11 @@ final class PbapClientStateMachine extends StateMachine { transitionTo(mDisconnecting); break; + case MSG_RESUME_DOWNLOAD: + mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD) + .sendToTarget(); + break; + default: Log.w(TAG,"Received unexpected message while Connected"); return NOT_HANDLED; @@ -312,6 +331,15 @@ final class PbapClientStateMachine extends StateMachine { sendMessage(MSG_DISCONNECT, device); } + public void resumeDownload() { + removeUncleanAccounts(); + sendMessage(MSG_RESUME_DOWNLOAD); + } + + void doQuit() { + quitNow(); + } + public int getConnectionState() { IState currentState = getCurrentState(); if (currentState instanceof Disconnected) { @@ -370,4 +398,28 @@ final class PbapClientStateMachine extends StateMachine { } return mCurrentDevice; } + + Context getContext() { + return mContext; + } + + private void removeUncleanAccounts() { + // Find all accounts that match the type "pbap" and delete them. + AccountManager accountManager = AccountManager.get(mContext); + Account[] accounts = accountManager.getAccountsByType( + mContext.getString(R.string.pbap_account_type)); + Log.w(TAG, "Found " + accounts.length + " unclean accounts"); + for (Account acc : accounts) { + Log.w(TAG, "Deleting " + acc); + // The device ID is the name of the account. + accountManager.removeAccountExplicitly(acc); + } + mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null); + + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mCurrentDevice: " + mCurrentDevice); + ProfileService.println(sb, "StateMachine: " + this.toString()); + } } -- 2.11.0