Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a3cfba9 authored by Joseph Pirozzo's avatar Joseph Pirozzo
Browse files

PBAP abort operations when disconnected

Push long running activities relating to the contacts database onto its
own thread such that the main service thread can remain responsive to
new bluetooth requests.

Bug: 27697738
Change-Id: I0202962a49d6a0279b5672152955840dd02bdf31
parent 00a465ed
Loading
Loading
Loading
Loading
+4 −9
Original line number Diff line number Diff line
@@ -48,14 +48,14 @@ public class CallLogPullRequest extends PullRequest {
    }

    @Override
    public void onPullComplete(boolean success, List<VCardEntry> entries) {
        if (entries == null) {
    public void onPullComplete() {
        if (mEntries == null) {
            Log.e(TAG, "onPullComplete entries is null.");
            return;
        }

        if (DBG) {
            Log.d(TAG, "onPullComplete with " + entries.size() + " count.");
            Log.d(TAG, "onPullComplete with " + mEntries.size() + " count.");
        }
        int type;
        try {
@@ -70,13 +70,8 @@ public class CallLogPullRequest extends PullRequest {
                return;
            }

            if (entries == null) {
                // Nothing to do. Return.
                return;
            }

            ArrayList<ContentProviderOperation> ops = new ArrayList<>();
            for (VCardEntry vcard : entries) {
            for (VCardEntry vcard : mEntries) {
                List<PhoneData> phones = vcard.getPhoneList();
                if (phones == null || phones.size() != 1) {
                    Log.d(TAG, "Incorrect number of phones: " + vcard);
+133 −120
Original line number Diff line number Diff line
@@ -27,7 +27,11 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.ContactsContract;
@@ -42,8 +46,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;

import java.lang.InterruptedException;
import java.lang.Thread;
/**
 * These are the possible paths that can be pulled:
@@ -58,26 +61,29 @@ import java.lang.Thread;
 */
public class PbapPCEClient  implements PbapHandler.PbapListener {
    private static final String TAG = "PbapPCEClient";
    private static final boolean DBG = false;
    private static final boolean DBG = true;
    private final Queue<PullRequest> mPendingRequests = new ArrayDeque<PullRequest>();
    private final AtomicBoolean mPendingPull = new AtomicBoolean(false);
    private BluetoothDevice mDevice;
    private BluetoothPbapClient mClient;
    private boolean mClientConnected = false;
    private PbapHandler mHandler;
    private Handler mSelfHandler;
    private PullRequest mLastPull;
    private HandlerThread mContactHandlerThread;
    private Handler mContactHandler;
    private Account mAccount = null;
    private Context mContext = null;
    private AccountManager mAccountManager;
    private DeleteCallLogTask mDeleteCallLogTask;

    PbapPCEClient(Context context) {
        mContext = context;
        mSelfHandler = new Handler(mContext.getMainLooper());
        mHandler = new PbapHandler(this);
        mAccountManager = AccountManager.get(mContext);

        mContactHandlerThread = new HandlerThread("PBAP contact handler",
                Process.THREAD_PRIORITY_BACKGROUND);
        mContactHandlerThread.start();
        mContactHandler = new ContactHandler(mContactHandlerThread.getLooper());
    }

    public int getConnectionState() {
@@ -109,46 +115,30 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
        return mDevice;
    }

    private synchronized boolean maybePull() {
        if (DBG) {
            Log.d(TAG,"Attempting to Pull");
        }
        if (!mClientConnected) {
            Log.w(TAG, "Client not connected yet -- will execute on next cycle.");
            return false;
        }
        return maybePullLocked();
    }

    private boolean maybePullLocked() {
    private boolean processNextRequest() {
        if (DBG) {
            Log.d(TAG,"maybePullLocked()");
            Log.d(TAG,"processNextRequest()");
        }
        if (mPendingPull.compareAndSet(false, true)) {
        if (mPendingRequests.isEmpty()) {
                mPendingPull.set(false);
            return false;
        }
            if (mClient != null) {
        if (mClient != null  && mClient.getState() ==
                BluetoothPbapClient.ConnectionState.CONNECTED) {
            mLastPull = mPendingRequests.remove();
            if (DBG) {
                Log.d(TAG, "Pulling phone book from: " + mLastPull.path);
            }
            return mClient.pullPhoneBook(mLastPull.path);
        }
        }
        return false;
    }

    private void pullComplete() {
        mPendingPull.set(false);
        maybePull();
    }

    @Override
    public void onPhoneBookPullDone(List<VCardEntry> entries) {
        mLastPull.onPullComplete(true, entries);
        pullComplete();
        mLastPull.setResults(entries);
        mContactHandler.obtainMessage(ContactHandler.EVENT_ADD_CONTACTS,mLastPull).sendToTarget();
        processNextRequest();
    }

    @Override
@@ -156,8 +146,7 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
        if (DBG) {
            Log.d(TAG, "Error, mLastPull = "  + mLastPull);
        }
        mLastPull.onPullComplete(false, null);
        pullComplete();
        processNextRequest();
    }

    @Override
@@ -167,7 +156,9 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
            // If we are disconnected then whatever the current device is we should simply clean up.
            handleDisconnect(null);
        }
        if (mClientConnected == true) maybePullLocked();
        if (mClientConnected == true) {
            processNextRequest();
        }
    }

    public void handleConnect(BluetoothDevice device) {
@@ -184,26 +175,15 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
            Log.w(TAG, "Got a connected event for the same device. Ignoring!");
            return;
        }
        resetState();
        // Cancel any pending delete tasks that might race.
        if (mDeleteCallLogTask != null) {
            mDeleteCallLogTask.cancel(true);
        }
        mDeleteCallLogTask = new DeleteCallLogTask();

        // Cleanup any existing accounts if we get a connected event but previous account state was
        // left hanging (such as unclean shutdown).
        removeUncleanAccounts();

        // Update the device.
        mDevice = device;
        mClient = new BluetoothPbapClient(mDevice, mAccount, mHandler);
        mClient.connect();

        // Add the account. This should give us a place to stash the data.
        addAccount(device.getAddress());
        mAccount = new Account(device.getAddress(), mContext.getString(R.string.pbap_account_type));
        mContactHandler.obtainMessage(ContactHandler.EVENT_ADD_ACCOUNT,mAccount).sendToTarget();
        downloadPhoneBook();
        downloadCallLogs();
        mClient.connect();
    }

    public void handleDisconnect(BluetoothDevice device) {
@@ -220,11 +200,6 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
                       " disconnecting device = " + device);
            return;
        }

        if (device != null) {
            removeAccount(mAccount);
            mAccount = null;
        }
        resetState();
    }

@@ -235,52 +210,47 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
            return;
        }
        // Device is NULL, we go on remove any unclean shutdown accounts.
        removeUncleanAccounts();
        resetState();
        mContactHandler.obtainMessage(ContactHandler.EVENT_CLEANUP).sendToTarget();
    }

    private void resetState() {
        if (DBG) {
            Log.d(TAG,"resetState()");
        }
        if (mClient != null) {
            // This should abort any inflight messages.
            mClient.disconnect();
        }
        mClient = null;
        mClientConnected = false;
        if (mDeleteCallLogTask != null &&
            mDeleteCallLogTask.getStatus() == AsyncTask.Status.PENDING) {
            mDeleteCallLogTask.execute();
        }

        mContactHandler.removeCallbacksAndMessages(null);
        mContactHandlerThread.interrupt();
        mContactHandler.obtainMessage(ContactHandler.EVENT_CLEANUP).sendToTarget();

        mDevice = null;
        mAccount = null;
        mPendingRequests.clear();
        if (DBG) {
            Log.d(TAG,"resetState Complete");

        }

    private void removeUncleanAccounts() {
        // Find all accounts that match the type "pbap" and delete them. This section is
        // executed only if the device was shut down in an unclean state and contacts persisted.
        Account[] accounts =
            mAccountManager.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.
            removeAccount(acc);
        }
    }

    private void downloadCallLogs() {
        // Download Incoming Call Logs.
        CallLogPullRequest ichCallLog = new CallLogPullRequest(mContext, BluetoothPbapClient.ICH_PATH);
        CallLogPullRequest ichCallLog =
                new CallLogPullRequest(mContext, BluetoothPbapClient.ICH_PATH);
        addPullRequest(ichCallLog);

        // Downoad Outgoing Call Logs.
        CallLogPullRequest ochCallLog = new CallLogPullRequest(mContext, BluetoothPbapClient.OCH_PATH);
        CallLogPullRequest ochCallLog =
                new CallLogPullRequest(mContext, BluetoothPbapClient.OCH_PATH);
        addPullRequest(ochCallLog);

        // Downoad Missed Call Logs.
        CallLogPullRequest mchCallLog = new CallLogPullRequest(mContext, BluetoothPbapClient.MCH_PATH);
        CallLogPullRequest mchCallLog =
                new CallLogPullRequest(mContext, BluetoothPbapClient.MCH_PATH);
        addPullRequest(mchCallLog);
    }

@@ -290,24 +260,82 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
        addPullRequest(pb);
    }

    private class DeleteCallLogTask extends AsyncTask<Void, Void, Void> {
    private void addPullRequest(PullRequest r) {
        if (DBG) {
            Log.d(TAG, "pull request mClient=" + mClient + " connected= " +
                    mClientConnected + " mDevice=" + mDevice + " path= " + r.path);
        }
        if (mClient == null || mDevice == null) {
            // It seems we want to pull but the bt connection isn't up, fail it
            // immediately.
            Log.w(TAG, "aborting pull request.");
            return;
        }
        mPendingRequests.add(r);
    }

    private class ContactHandler extends Handler {
        public static final int EVENT_ADD_ACCOUNT = 1;
        public static final int EVENT_ADD_CONTACTS = 2;
        public static final int EVENT_CLEANUP = 3;

        public ContactHandler(Looper looper) {
          super(looper);
        }

        @Override
        protected Void doInBackground(Void... unused) {
            if (isCancelled()) {
                return null;
        public void handleMessage(Message msg) {
            if (DBG) {
                Log.d(TAG, "Contact Handler Message " + msg.what + " with " + msg.obj);
            }
            switch (msg.what) {
                case EVENT_ADD_ACCOUNT:
                    if (msg.obj instanceof Account) {
                        Account account = (Account) msg.obj;
                        addAccount(account);
                    }
                    break;

                case EVENT_ADD_CONTACTS:
                    if (msg.obj instanceof PullRequest) {
                        PullRequest req = (PullRequest) msg.obj;
                        req.onPullComplete();
                    }
                    else {
                        Log.w(TAG, "invalid Instance in contact handler");
                    }
                    break;

                case EVENT_CLEANUP:
                    Thread.currentThread().interrupted();  //clear state of interrupt.
                    removeUncleanAccounts();
                    mContext.getContentResolver().delete(CallLog.Calls.CONTENT_URI, null, null);
                    if (DBG) {
                        Log.d(TAG, "Call logs deleted.");
                    }
            return null;
                    break;

                default:
                    Log.e(TAG, "Unknown Request to Contact Handler");
                    break;
            }
        }

        private void removeUncleanAccounts() {
            // Find all accounts that match the type "pbap" and delete them. This section is
            // executed only if the device was shut down in an unclean state and contacts persisted.
            Account[] accounts =
                mAccountManager.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.
                removeAccount(acc);
            }
        }

    private boolean addAccount(String id) {
        mAccount = new Account(id, mContext.getString(R.string.pbap_account_type));
        if (mAccountManager.addAccountExplicitly(mAccount, null, null)) {
        private boolean addAccount(Account account) {
            if (mAccountManager.addAccountExplicitly(account, null, null)) {
                if (DBG) {
                    Log.d(TAG, "Added account " + mAccount);
                }
@@ -326,20 +354,5 @@ public class PbapPCEClient implements PbapHandler.PbapListener {
            Log.e(TAG, "Failed to remove account " + mAccount);
            return false;
        }

    public void addPullRequest(PullRequest r) {
        if (DBG) {
            Log.d(TAG, "pull request mClient=" + mClient + " connected= " +
                    mClientConnected + " mDevice=" + mDevice + " path= " + r.path);
        }
        if (mClient == null || mDevice == null) {
            // It seems we want to pull but the bt connection isn't up, fail it
            // immediately.
            Log.w(TAG, "aborting pull request.");
            r.onPullComplete(false, null);
            return;
        }
        mPendingRequests.add(r);
        maybePull();
   }
}
+11 −9
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.util.Log;

import com.android.vcard.VCardEntry;

import java.lang.InterruptedException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -119,9 +120,12 @@ public class PhonebookPullRequest extends PullRequest {
    }

    private void addContacts(List<PhonebookEntry> entries)
            throws RemoteException, OperationApplicationException {
            throws RemoteException, OperationApplicationException, InterruptedException {
        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        for (PhonebookEntry e : entries) {
            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedException();
            }
            int index = ops.size();
            // Add an entry.
            ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
@@ -177,27 +181,23 @@ public class PhonebookPullRequest extends PullRequest {
    }

    @Override
    public void onPullComplete(boolean success, List<VCardEntry> entries) {
        if (entries == null) {
    public void onPullComplete() {
        if (mEntries == null) {
            Log.e(TAG, "onPullComplete entries is null.");
            return;
        }

        if (DBG) {
            Log.d(TAG, "onPullComplete with " + entries.size() + " count.");
            Log.d(TAG, "onPullComplete with " + mEntries.size() + " count.");
        }
        try {
            if (!success) {
                Log.e(TAG, "Pull finished with errors.");
                return;
            }

            HashMap<PhonebookEntry.Name, PhonebookEntry> contacts = fetchExistingContacts();

            List<PhonebookEntry> contactsToAdd = new ArrayList<PhonebookEntry>();
            List<PhonebookEntry> contactsToDelete = new ArrayList<PhonebookEntry>();

            for (VCardEntry e : entries) {
            for (VCardEntry e : mEntries) {
                PhonebookEntry current = new PhonebookEntry(e);
                PhonebookEntry.Name key = current.name;

@@ -229,6 +229,8 @@ public class PhonebookPullRequest extends PullRequest {
                    + " delete=" + contactsToDelete.size());
        } catch (OperationApplicationException | RemoteException | NumberFormatException e) {
            Log.d(TAG, "Got exception: ", e);
        } catch (InterruptedException e) {
            Log.d(TAG, "Interrupted durring insert.");
        } finally {
            complete = true;
        }
+6 −1
Original line number Diff line number Diff line
@@ -21,11 +21,16 @@ import java.util.List;

public abstract class PullRequest {
    public String path;
    public abstract void onPullComplete(boolean success, List<VCardEntry> entries);
    protected List<VCardEntry> mEntries;
    public abstract void onPullComplete();

    @Override
    public String toString() {
        return "PullRequest: { path=" + path + " }";
    }

    public void setResults(List<VCardEntry> results) {
        mEntries = results;
    }
}