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

Commit 90b792d3 authored by Sal Savage's avatar Sal Savage
Browse files

Batch call log insertion operations

This change aligns call log insertion with contacts insertion with a
batching strategy. This helps us avoid binder transaction limits when
inserting call logs and can provide batched loading of logs when the
remote device has many.

Bug: 376461939
Bug: 380132523
Test: atest com.android.bluetooth.pbapclient
Change-Id: I5a9fcf779b4d4b206132a8c76204647c53310cfb
parent 811f3370
Loading
Loading
Loading
Loading
+108 −44
Original line number Diff line number Diff line
@@ -224,6 +224,13 @@ class PbapClientContactsStorage {
     *
     * <p>This function also associates the phonebook metadata with the contact for easy
     * per-phonebook cleanup operations.
     *
     * <p>Contacts are inserted in smaller batches so they can be loaded in chunks as opposed to
     * shown all at once in the UI. This also prevents us from hitting the binder transaction limit.
     *
     * @param account The account to insert contacts against
     * @param phonebook The phonebook these contacts belong to
     * @param contacts The list of contacts to insert
     */
    private boolean insertContacts(Account account, String phonebook, List<VCardEntry> contacts) {
        if (!mStorageInitialized) {
@@ -374,11 +381,15 @@ class PbapClientContactsStorage {
    /**
     * Insert call history entries of a given type
     *
     * <p>These call logs are inserted in smaller batches so they can be loaded in chunks as opposed
     * to shown all at once in the UI. This also prevents us from hitting the binder transaction
     * limit
     *
     * @param account The account to insert call logs against
     * @param type The type of call history provided
     * @param history The call history to insert
     * @param type The type of call provided
     * @param history The list of calls to add
     * @return True if successful, False otherwise
     */
    @SuppressWarnings("JavaUtilDate") // TODO: b/365629730 -- prefer Instant or LocalDate
    private boolean insertCallHistory(Account account, int type, List<VCardEntry> history) {
        if (!mStorageInitialized) {
            Log.w(TAG, "insertCallHistory: Failed, storage not ready");
@@ -405,18 +416,82 @@ class PbapClientContactsStorage {
        try {
            Log.i(
                    TAG,
                    "insertCallHistory: Inserting call history, type="
                    "insertCallHistory: inserting call history, account="
                            + account
                            + ", type="
                            + type
                            + ", count="
                            + history.size());

            ArrayList<ContentProviderOperation> ops = new ArrayList<>();
            for (VCardEntry vcard : history) {
            ContentResolver contactsProvider = mContext.getContentResolver();
            ArrayList<ContentProviderOperation> operations = new ArrayList<>();

            // Group insert operations together to minimize inter process communication and improve
            // processing time.
            for (VCardEntry callLog : history) {
                if (Thread.currentThread().isInterrupted()) {
                    Log.e(TAG, "insertCallHistory: Interrupted during insert");
                    break;
                }

                // Append current call to list of insert operations.
                int numberOfOperations = operations.size();
                constructInsertOperationsForCallLog(account, type, callLog, operations);

                if (operations.size() >= CONTACTS_INSERT_BATCH_SIZE) {
                    Log.i(
                            TAG,
                            "insertCallHistory: batch full, operations.size()="
                                    + operations.size()
                                    + ", batch_size="
                                    + CONTACTS_INSERT_BATCH_SIZE);

                    // If we have exceeded the limit of the insert operations, remove the latest
                    // call and submit.
                    operations.subList(numberOfOperations, operations.size()).clear();

                    contactsProvider.applyBatch(CallLog.AUTHORITY, operations);

                    // Re-add the current call log operation(s) to the list
                    operations = constructInsertOperationsForCallLog(account, type, callLog, null);

                    Log.i(
                            TAG,
                            "insertCallHistory: batch complete, operations.size()="
                                    + operations.size());
                }
            }

            // Apply any unsubmitted calls
            if (operations.size() > 0) {
                contactsProvider.applyBatch(CallLog.AUTHORITY, operations);
                operations.clear();
            }
            Log.i(TAG, "insertCallHistory: insert complete, count=" + history.size());
        } catch (OperationApplicationException | RemoteException | NumberFormatException e) {
            Log.e(TAG, "insertCallHistory: Exception occurred while processing call log pull: ", e);
            return false;
        }
        return true;
    }

    // TODO: b/365629730 -- JavaUtilDate: prefer Instant or LocalDate
    // NonApiType: For convenience, as the applyBatch API actually takes an ArrayList above
    @SuppressWarnings({"JavaUtilDate", "NonApiType"})
    private ArrayList<ContentProviderOperation> constructInsertOperationsForCallLog(
            Account account,
            int type,
            VCardEntry call,
            ArrayList<ContentProviderOperation> operations) {
        if (operations == null) {
            operations = new ArrayList<ContentProviderOperation>();
        }

        ContentValues values = new ContentValues();
                values.put(CallLog.Calls.TYPE, type);
        values.put(Calls.PHONE_ACCOUNT_ID, account.name);
        values.put(CallLog.Calls.TYPE, type);

                List<PhoneData> phones = vcard.getPhoneList();
        List<PhoneData> phones = call.getPhoneList();
        if (phones == null
                || phones.get(0).getNumber().equals(";")
                || phones.get(0).getNumber().length() == 0) {
@@ -426,7 +501,7 @@ class PbapClientContactsStorage {
            values.put(CallLog.Calls.NUMBER, phoneNumber);
        }

                List<Pair<String, String>> irmc = vcard.getUnknownXData();
        List<Pair<String, String>> irmc = call.getUnknownXData();
        SimpleDateFormat parser = new SimpleDateFormat(TIMESTAMP_FORMAT);
        if (irmc != null) {
            for (Pair<String, String> pair : irmc) {
@@ -440,24 +515,13 @@ class PbapClientContactsStorage {
            }
        }

                ops.add(
        operations.add(
                ContentProviderOperation.newInsert(CallLog.Calls.CONTENT_URI)
                        .withValues(values)
                        .withYieldAllowed(true)
                        .build());
            }

            mContext.getContentResolver().applyBatch(CallLog.AUTHORITY, ops);
            Log.d(TAG, "Inserted call logs, type=" + type);
        } catch (RemoteException | OperationApplicationException e) {
            Log.w(TAG, "Failed to insert call log, type=" + type, e);
            return false;
        } finally {
            synchronized (this) {
                this.notify();
            }
        }
        return true;
        return operations;
    }

    /**