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

Commit 34138460 authored by Joseph Pirozzo's avatar Joseph Pirozzo Committed by Sanket Agarwal
Browse files

PBAP vcard parser

Use the built in Vcard parser library for parsing Vcards rather than a
locally defined one.  The Vcard parser library includes support for
photos.

bug: 28177843
Change-Id: I3f335ff12ceec12e51a2b8628c320ede06342259
(cherry picked from commit e14009c22dfce52531f4e34fc1d58a9440539140)
parent f15281ea
Loading
Loading
Loading
Loading
+25 −169
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.pbapclient;

import com.android.vcard.VCardEntry;

import android.accounts.Account;
import com.android.bluetooth.pbapclient.BluetoothPbapClient;
import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.OperationApplicationException;
@@ -42,143 +56,14 @@ public class PhonebookPullRequest extends PullRequest {
        path = BluetoothPbapClient.PB_PATH;
    }

    private PhonebookEntry fetchContact(String id) {
        PhonebookEntry entry = new PhonebookEntry();
        entry.id = id;
        Cursor c = null;
        try {
            c = mContext.getContentResolver().query(
                    Data.CONTENT_URI,
                    null,
                    Data.RAW_CONTACT_ID + " = ?",
                    new String[] { id },
                    null);
            if (c != null) {
                int mimeTypeIndex = c.getColumnIndex(Data.MIMETYPE);
                int familyNameIndex = c.getColumnIndex(StructuredName.FAMILY_NAME);
                int givenNameIndex = c.getColumnIndex(StructuredName.GIVEN_NAME);
                int middleNameIndex = c.getColumnIndex(StructuredName.MIDDLE_NAME);
                int prefixIndex = c.getColumnIndex(StructuredName.PREFIX);
                int suffixIndex = c.getColumnIndex(StructuredName.SUFFIX);

                int phoneTypeIndex = c.getColumnIndex(Phone.TYPE);
                int phoneNumberIndex = c.getColumnIndex(Phone.NUMBER);

                while (c.moveToNext()) {
                    String mimeType = c.getString(mimeTypeIndex);
                    if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
                        entry.name.family = c.getString(familyNameIndex);
                        entry.name.given = c.getString(givenNameIndex);
                        entry.name.middle = c.getString(middleNameIndex);
                        entry.name.prefix = c.getString(prefixIndex);
                        entry.name.suffix = c.getString(suffixIndex);
                    } else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
                        PhonebookEntry.Phone p = new PhonebookEntry.Phone();
                        p.type = c.getInt(phoneTypeIndex);
                        p.number = c.getString(phoneNumberIndex);
                        entry.phones.add(p);
                    }
                }
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return entry;
    }

    private HashMap<PhonebookEntry.Name, PhonebookEntry> fetchExistingContacts() {
        HashMap<PhonebookEntry.Name, PhonebookEntry> entries = new HashMap<>();

        Cursor c = null;
        try {
            // First find all the contacts present. Fetch all rows.
            Uri uri = RawContacts.CONTENT_URI.buildUpon()
                    .appendQueryParameter(RawContacts.ACCOUNT_NAME, mAccount.name)
                    .appendQueryParameter(RawContacts.ACCOUNT_TYPE, mAccount.type)
                    .build();
            // First get all the raw contact ids.
            c = mContext.getContentResolver().query(uri,
                    new String[]  { RawContacts._ID },
                    null, null, null);

            if (c != null) {
                while (c.moveToNext()) {
                    // For each raw contact id, fetch all the data.
                    PhonebookEntry e = fetchContact(c.getString(0));
                    entries.put(e.name, e);
                }
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }

        return entries;
    }

    private void addContacts(List<PhonebookEntry> entries)
    // TODO: Apply operations together if possible.
    private void addContact(VCardEntry e)
        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)
                    .withValue(RawContacts.ACCOUNT_TYPE, mAccount.type)
                    .withValue(RawContacts.ACCOUNT_NAME, mAccount.name)
                    .build());

            // Populate the name.
            ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
                    .withValueBackReference(Data.RAW_CONTACT_ID, index)
                    .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
                    .withValue(StructuredName.FAMILY_NAME , e.name.family)
                    .withValue(StructuredName.GIVEN_NAME , e.name.given)
                    .withValue(StructuredName.MIDDLE_NAME , e.name.middle)
                    .withValue(StructuredName.PREFIX , e.name.prefix)
                    .withValue(StructuredName.SUFFIX , e.name.suffix)
                    .build());

            // Populate the phone number(s) if any.
            for (PhonebookEntry.Phone p : e.phones) {
                ops.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
                        .withValueBackReference(Data.RAW_CONTACT_ID, index)
                        .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
                        .withValue(Phone.NUMBER, p.number)
                        .withValue(Phone.TYPE, p.type)
                        .build());
            }

            // Commit MAX_OPS at a time so that the binder transaction doesn't get too large.
            if (ops.size() > MAX_OPS) {
        ArrayList<ContentProviderOperation> ops =
                e.constructInsertOperations(mContext.getContentResolver(), null);
        mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        ops.clear();
    }
        }

        if (ops.size() > 0) {
            // Commit remaining entries.
            mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
        }
    }

    private void deleteContacts(List<PhonebookEntry> entries)
            throws RemoteException, OperationApplicationException {
        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
        for (PhonebookEntry e : entries) {
            ops.add(ContentProviderOperation.newDelete(RawContacts.CONTENT_URI.buildUpon()
                        .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
                        .build())
                .withSelection(RawContacts._ID + "=?", new String[] { e.id })
                .build());
        }
        mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);
    }

    @Override
    public void onPullComplete() {
@@ -191,42 +76,13 @@ public class PhonebookPullRequest extends PullRequest {
            Log.d(TAG, "onPullComplete with " + mEntries.size() + " count.");
        }
        try {

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

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

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

                PhonebookEntry contact = contacts.get(key);
                if (contact == null) {
                    contactsToAdd.add(current);
                } else if (!contact.equals(current)) {
                    // Instead of trying to figure out what changed on an update, do a delete
                    // and an add. Sure, it churns contact ids but a contact being updated
                    // while someone is connected is a low enough frequency event that the
                    // complexity of doing an update is just not worth it.
                    contactsToAdd.add(current);
                    // Don't remove it from the hashmap so it will get deleted.
                } else {
                    contacts.remove(key);
                }
            }
            contactsToDelete.addAll(contacts.values());

            if (!contactsToDelete.isEmpty()) {
                deleteContacts(contactsToDelete);
                if (Thread.currentThread().isInterrupted()) {
                    throw new InterruptedException();
                }

            if (!contactsToAdd.isEmpty()) {
                addContacts(contactsToAdd);
                addContact(e);
            }

            Log.d(TAG, "Sync complete: add=" + contactsToAdd.size()
                    + " delete=" + contactsToDelete.size());
            Log.d(TAG, "Sync complete: add=" + mEntries.size());
        } catch (OperationApplicationException | RemoteException | NumberFormatException e) {
            Log.d(TAG, "Got exception: ", e);
        } catch (InterruptedException e) {