*/
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;
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
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) {
mDevice = device;
mPbapClientStateMachine = stateMachine;
mAuth = new BluetoothPbapObexAuthenticator(this);
+ mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
}
@Override
/* 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();
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");
}
}
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() {
}
} 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);
}
}
}
*/
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;
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;
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;
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,
}
break;
+ case MSG_RESUME_DOWNLOAD:
+ // Do nothing.
+ break;
+
default:
Log.w(TAG,"Received unexpected message while disconnected.");
return NOT_HANDLED;
}
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;
mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_CONNECT)
.sendToTarget();
sendMessageDelayed(MSG_CONNECT_TIMEOUT, CONNECT_TIMEOUT);
- // TODO: create account
}
@Override
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;
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
mConnectionHandler.abort();
break;
+ case MSG_RESUME_DOWNLOAD:
+ // Do nothing.
+ break;
+
default:
Log.w(TAG,"Received unexpected message while Disconnecting");
return NOT_HANDLED;
onConnectionStateChanged(mCurrentDevice, mMostRecentState,
BluetoothProfile.STATE_CONNECTED);
mMostRecentState = BluetoothProfile.STATE_CONNECTED;
- // mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD)
- // .sendToTarget();
+ mConnectionHandler.obtainMessage(PbapClientConnectionHandler.MSG_DOWNLOAD)
+ .sendToTarget();
}
@Override
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;
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) {
}
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());
+ }
}