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

Commit eacbe8e4 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge changes Idd78c9b3,I85b8e732,Icd3ea5ef,I9a8896c6 into main

* changes:
  Rename RequestPhoneBook* classes to RequestPhonebook*
  Create PbapPhonebookMetadata to hold size and version information
  Create PbapApplicationParameters to abstract request params
  Rename PbapClientVcardList to PbapPhonebook and update test coverage
parents b659e911 733678d3
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -69,11 +69,11 @@ public class CallLogPullRequest extends PullRequest {
        Log.d(TAG, "onPullComplete with " + mEntries.size() + " entries");
        int type;
        try {
            if (path.equals(PbapClientConnectionHandler.ICH_PATH)) {
            if (path.equals(PbapPhonebook.ICH_PATH)) {
                type = CallLog.Calls.INCOMING_TYPE;
            } else if (path.equals(PbapClientConnectionHandler.OCH_PATH)) {
            } else if (path.equals(PbapPhonebook.OCH_PATH)) {
                type = CallLog.Calls.OUTGOING_TYPE;
            } else if (path.equals(PbapClientConnectionHandler.MCH_PATH)) {
            } else if (path.equals(PbapPhonebook.MCH_PATH)) {
                type = CallLog.Calls.MISSED_TYPE;
            } else {
                Log.w(TAG, "Unknown path type:" + path);
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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;

public class PbapApplicationParameters {
    private static final String TAG = PbapApplicationParameters.class.getSimpleName();

    // Max size for a phonebook, which determines the max size of a batch and an offset. This comes
    // from the fact that each field is 2 bytes -> 16 bits -> [0, 65535] (i.e, inclusive)
    public static final int MAX_PHONEBOOK_SIZE = 65535;

    // Application Parameter Header keys (PBAP 1.2.3, Section 6.2.1)
    public static final byte OAP_ORDER = 0x01;
    public static final byte OAP_SEARCH_VALUE = 0x02;
    public static final byte OAP_SEARCH_PROPERTY = 0x03;
    public static final byte OAP_MAX_LIST_COUNT = 0x04;
    public static final byte OAP_LIST_START_OFFSET = 0x05;
    public static final byte OAP_PROPERTY_SELECTOR = 0x06;
    public static final byte OAP_FORMAT = 0x07;
    public static final byte OAP_PHONEBOOK_SIZE = 0x08;
    public static final byte OAP_NEW_MISSED_CALLS = 0x09;
    public static final byte OAP_PRIMARY_FOLDER_VERSION = 0x0A;
    public static final byte OAP_SECONDARY_FOLDER_VERSION = 0x0B;
    public static final byte OAP_VCARD_SELECTOR = 0x0C;
    public static final byte OAP_DATABASE_IDENTIFIER = 0x0D;
    public static final byte OAP_VCARD_SELECTOR_OPERATOR = 0x0E;
    public static final byte OAP_RESET_NEW_MISSED_CALLS = 0x0F;
    public static final byte OAP_PBAP_SUPPORTED_FEATURES = 0x10;

    // Property Selector "filter" constants, PBAP 1.2.3, section 5.1.4.1
    public static final long PROPERTIES_ALL = 0;
    public static final long PROPERTY_VERSION = 1 << 0;
    public static final long PROPERTY_FN = 1 << 1;
    public static final long PROPERTY_N = 1 << 2;
    public static final long PROPERTY_PHOTO = 1 << 3;
    public static final long PROPERTY_ADR = 1 << 5;
    public static final long PROPERTY_TEL = 1 << 7;
    public static final long PROPERTY_EMAIL = 1 << 8;
    public static final long PROPERTY_NICKNAME = 1 << 23;

    // MaxListCount field, PBAP 1.2.3, Section 5.3.4.4: "0" signifies to the PSE that the PCE is
    // requesting the number of indexes in the phonebook of interest that are actually used
    // (i.e. indexes that correspond to non-NULL entries). Using this causes other parameters to be
    // ignored. Only metadata will be returned, like the size.
    public static final int RETURN_SIZE_ONLY = 0;

    private final long mProperties; // 64-bit property selector bit field, 8 bytes
    private final byte mFormat; // Vcard format, 0 or 1, 1 byte, From PbapVcardList object
    private final int mMaxListCount; // The total number of items to fetch, for batching, 2 bytes
    private final int mListStartOffset; // The item index to start at, for batching, 2 bytes

    PbapApplicationParameters(long properties, byte format, int maxListCount, int listStartOffset) {
        if (maxListCount < 0 || maxListCount > MAX_PHONEBOOK_SIZE) {
            throw new IllegalArgumentException("maxListCount should be [0, 65535]");
        }
        if (listStartOffset < 0 || listStartOffset > MAX_PHONEBOOK_SIZE) {
            throw new IllegalArgumentException("listStartOffset should be [0, 65535]");
        }
        if (format != PbapPhonebook.FORMAT_VCARD_21 && format != PbapPhonebook.FORMAT_VCARD_30) {
            throw new IllegalArgumentException("VCard format must be 2.1 or 3.0");
        }

        mProperties = properties;
        mFormat = format;
        mMaxListCount = maxListCount;
        mListStartOffset = listStartOffset;
    }

    public long getPropertySelectorMask() {
        return mProperties;
    }

    public byte getVcardFormat() {
        return mFormat;
    }

    public int getMaxListCount() {
        return mMaxListCount;
    }

    public int getListStartOffset() {
        return mListStartOffset;
    }

    public static String propertiesToString(long properties) {
        if (properties == PROPERTIES_ALL) {
            return "PROPERTIES_ALL";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("[");
        if ((properties & PROPERTY_VERSION) != 0) {
            sb.append("PROPERTY_VERSION ");
        }
        if ((properties & PROPERTY_FN) != 0) {
            sb.append("PROPERTY_FN ");
        }
        if ((properties & PROPERTY_N) != 0) {
            sb.append("PROPERTY_N ");
        }
        if ((properties & PROPERTY_PHOTO) != 0) {
            sb.append("PROPERTY_PHOTO ");
        }
        if ((properties & PROPERTY_ADR) != 0) {
            sb.append("PROPERTY_ADR ");
        }
        if ((properties & PROPERTY_TEL) != 0) {
            sb.append("PROPERTY_TEL ");
        }
        if ((properties & PROPERTY_EMAIL) != 0) {
            sb.append("PROPERTY_EMAIL ");
        }
        if ((properties & PROPERTY_NICKNAME) != 0) {
            sb.append("PROPERTY_NICKNAME ");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("]");
        return sb.toString();
    }

    @Override
    public String toString() {
        return "<"
                + TAG
                + (" properties=" + propertiesToString(getPropertySelectorMask()))
                + (" format=" + getVcardFormat())
                + (" maxListCount=" + getMaxListCount())
                + (" listStartOffset=" + getListStartOffset())
                + ">";
    }
}
+39 −57
Original line number Diff line number Diff line
@@ -57,10 +57,6 @@ class PbapClientConnectionHandler extends Handler {
    // progress when Bluetooth stack is torn down.
    private static final int DEFAULT_BATCH_SIZE = 250;

    // Upper limit on the indices of the vcf cards/entries, inclusive,
    // i.e., valid indices are [0, 1, ... , UPPER_LIMIT]
    private static final int UPPER_LIMIT = 65535;

    static final int MSG_CONNECT = 1;
    static final int MSG_DISCONNECT = 2;
    static final int MSG_DOWNLOAD = 3;
@@ -90,47 +86,17 @@ class PbapClientConnectionHandler extends Handler {
    private static final int PBAP_FEATURE_DEFAULT_IMAGE_FORMAT = 0x00000200;
    private static final int PBAP_FEATURE_DOWNLOADING = 0x00000001;

    private static final long PBAP_FILTER_VERSION = 1 << 0;
    private static final long PBAP_FILTER_FN = 1 << 1;
    private static final long PBAP_FILTER_N = 1 << 2;
    private static final long PBAP_FILTER_PHOTO = 1 << 3;
    private static final long PBAP_FILTER_ADR = 1 << 5;
    private static final long PBAP_FILTER_TEL = 1 << 7;
    private static final long PBAP_FILTER_EMAIL = 1 << 8;
    private static final long PBAP_FILTER_NICKNAME = 1 << 23;

    private static final int PBAP_SUPPORTED_FEATURE =
            PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_DOWNLOADING;
    private static final long PBAP_REQUESTED_FIELDS =
            PBAP_FILTER_VERSION
                    | PBAP_FILTER_FN
                    | PBAP_FILTER_N
                    | PBAP_FILTER_PHOTO
                    | PBAP_FILTER_ADR
                    | PBAP_FILTER_EMAIL
                    | PBAP_FILTER_TEL
                    | PBAP_FILTER_NICKNAME;

    @VisibleForTesting static final int L2CAP_INVALID_PSM = -1;

    public static final String PB_PATH = "telecom/pb.vcf";
    public static final String FAV_PATH = "telecom/fav.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 String SIM_PB_PATH = "SIM1/telecom/pb.vcf";
    public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf";
    public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf";
    public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf";

    // PBAP v1.2.3 Sec. 7.1.2
    private static final int SUPPORTED_REPOSITORIES_LOCALPHONEBOOK = 1 << 0;
    private static final int SUPPORTED_REPOSITORIES_SIMCARD = 1 << 1;
    private static final int SUPPORTED_REPOSITORIES_FAVORITES = 1 << 3;

    public static final int PBAP_V1_2 = 0x0102;
    public static final byte VCARD_TYPE_21 = 0;
    public static final byte VCARD_TYPE_30 = 1;

    private Account mAccount;
    private AccountManager mAccountManager;
@@ -250,19 +216,19 @@ class PbapClientConnectionHandler extends Handler {
                    return;
                }
                if (isRepositorySupported(SUPPORTED_REPOSITORIES_FAVORITES)) {
                    downloadContacts(FAV_PATH);
                    downloadContacts(PbapPhonebook.FAVORITES_PATH);
                }
                if (isRepositorySupported(SUPPORTED_REPOSITORIES_LOCALPHONEBOOK)) {
                    downloadContacts(PB_PATH);
                    downloadContacts(PbapPhonebook.LOCAL_PHONEBOOK_PATH);
                }
                if (isRepositorySupported(SUPPORTED_REPOSITORIES_SIMCARD)) {
                    downloadContacts(SIM_PB_PATH);
                    downloadContacts(PbapPhonebook.SIM_PHONEBOOK_PATH);
                }

                Map<String, Integer> callCounter = new HashMap<>();
                downloadCallLog(MCH_PATH, callCounter);
                downloadCallLog(ICH_PATH, callCounter);
                downloadCallLog(OCH_PATH, callCounter);
                downloadCallLog(PbapPhonebook.MCH_PATH, callCounter);
                downloadCallLog(PbapPhonebook.ICH_PATH, callCounter);
                downloadCallLog(PbapPhonebook.OCH_PATH, callCounter);
                break;

            default:
@@ -334,7 +300,7 @@ class PbapClientConnectionHandler extends Handler {

                if (mPseRec.getProfileVersion() >= PBAP_V1_2) {
                    oap.add(
                            PbapClientRequest.OAP_TAGID_PBAP_SUPPORTED_FEATURES,
                            PbapApplicationParameters.OAP_PBAP_SUPPORTED_FEATURES,
                            PBAP_SUPPORTED_FEATURE);
                }

@@ -381,14 +347,21 @@ class PbapClientConnectionHandler extends Handler {
            PhonebookPullRequest processor =
                    new PhonebookPullRequest(mPbapClientStateMachine.getContext());

            PbapApplicationParameters params =
                    new PbapApplicationParameters(
                            PbapApplicationParameters.PROPERTIES_ALL,
                            /* format, unused */ (byte) 0,
                            PbapApplicationParameters.RETURN_SIZE_ONLY,
                            /* list start offeset, start from beginning */ 0);

            // Download contacts in batches of size DEFAULT_BATCH_SIZE
            RequestPullPhoneBookSize requestPbSize =
                    new RequestPullPhoneBookSize(path, PBAP_REQUESTED_FIELDS);
            RequestPullPhonebookMetadata requestPbSize =
                    new RequestPullPhonebookMetadata(path, params);
            requestPbSize.execute(mObexSession);

            int numberOfContactsRemaining = requestPbSize.getSize();
            int numberOfContactsRemaining = requestPbSize.getMetadata().getSize();
            int startOffset = 0;
            if (PB_PATH.equals(path)) {
            if (PbapPhonebook.LOCAL_PHONEBOOK_PATH.equals(path)) {
                // PBAP v1.2.3, Sec 3.1.5. The first contact in pb is owner card 0.vcf, which we
                // do not want to download. The other phonebook objects (e.g., fav) don't have an
                // owner card, so they don't need an offset.
@@ -397,22 +370,24 @@ class PbapClientConnectionHandler extends Handler {
                numberOfContactsRemaining -= 1;
            }

            while ((numberOfContactsRemaining > 0) && (startOffset <= UPPER_LIMIT)) {
            while ((numberOfContactsRemaining > 0)
                    && (startOffset <= PbapApplicationParameters.MAX_PHONEBOOK_SIZE)) {
                int numberOfContactsToDownload =
                        Math.min(
                                Math.min(DEFAULT_BATCH_SIZE, numberOfContactsRemaining),
                                UPPER_LIMIT - startOffset + 1);
                RequestPullPhoneBook request =
                        new RequestPullPhoneBook(
                                path,
                                mAccount,
                                PBAP_REQUESTED_FIELDS,
                                VCARD_TYPE_30,
                                PbapApplicationParameters.MAX_PHONEBOOK_SIZE - startOffset + 1);

                params =
                        new PbapApplicationParameters(
                                PbapApplicationParameters.PROPERTIES_ALL,
                                PbapPhonebook.FORMAT_VCARD_30,
                                numberOfContactsToDownload,
                                startOffset);

                RequestPullPhonebook request = new RequestPullPhonebook(path, params, mAccount);
                request.execute(mObexSession);
                List<VCardEntry> vcards = request.getList();
                if (FAV_PATH.equals(path)) {
                if (PbapPhonebook.FAVORITES_PATH.equals(path)) {
                    // mark each vcard as a favorite
                    for (VCardEntry v : vcards) {
                        v.setStarred(true);
@@ -424,7 +399,8 @@ class PbapClientConnectionHandler extends Handler {
                startOffset += numberOfContactsToDownload;
                numberOfContactsRemaining -= numberOfContactsToDownload;
            }
            if ((startOffset > UPPER_LIMIT) && (numberOfContactsRemaining > 0)) {
            if ((startOffset > PbapApplicationParameters.MAX_PHONEBOOK_SIZE)
                    && (numberOfContactsRemaining > 0)) {
                Log.w(TAG, "Download contacts incomplete, index exceeded upper limit.");
            }
        } catch (IOException e) {
@@ -437,8 +413,14 @@ class PbapClientConnectionHandler extends Handler {
    @VisibleForTesting
    void downloadCallLog(String path, Map<String, Integer> callCounter) {
        try {
            RequestPullPhoneBook request =
                    new RequestPullPhoneBook(path, mAccount, 0, VCARD_TYPE_30, 0, 0);
            PbapApplicationParameters params =
                    new PbapApplicationParameters(
                            /* properties, unused for call logs */ 0,
                            PbapPhonebook.FORMAT_VCARD_30,
                            0,
                            0);

            RequestPullPhonebook request = new RequestPullPhonebook(path, params, mAccount);
            request.execute(mObexSession);
            CallLogPullRequest processor =
                    new CallLogPullRequest(
+0 −11
Original line number Diff line number Diff line
@@ -29,17 +29,6 @@ import java.io.InputStream;
abstract class PbapClientRequest {
    static final String TAG = PbapClientRequest.class.getSimpleName();

    protected static final byte OAP_TAGID_ORDER = 0x01;
    protected static final byte OAP_TAGID_SEARCH_VALUE = 0x02;
    protected static final byte OAP_TAGID_SEARCH_ATTRIBUTE = 0x03;
    protected static final byte OAP_TAGID_MAX_LIST_COUNT = 0x04;
    protected static final byte OAP_TAGID_LIST_START_OFFSET = 0x05;
    protected static final byte OAP_TAGID_FILTER = 0x06;
    protected static final byte OAP_TAGID_FORMAT = 0x07;
    protected static final byte OAP_TAGID_PHONEBOOK_SIZE = 0x08;
    protected static final byte OAP_TAGID_NEW_MISSED_CALLS = 0x09;
    protected static final byte OAP_TAGID_PBAP_SUPPORTED_FEATURES = 0x10;

    protected HeaderSet mHeaderSet;

    protected int mResponseCode;
+79 −11
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.bluetooth.pbapclient;

import android.accounts.Account;
import android.annotation.Nullable;
import android.util.Log;

import com.android.vcard.VCardConfig;
@@ -35,13 +36,36 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

class PbapClientVcardList {
    private static final String TAG = PbapClientVcardList.class.getSimpleName();
public class PbapPhonebook {
    private static final String TAG = PbapPhonebook.class.getSimpleName();

    // {@link BufferedInputStream#DEFAULT_BUFFER_SIZE} is not public
    private static final int BIS_DEFAULT_BUFFER_SIZE = 8192;

    // Phonebooks, including call history. See PBAP 1.2.3, Section 3.1.2
    public static final String LOCAL_PHONEBOOK_PATH = "telecom/pb.vcf"; // Device phonebook
    public static final String FAVORITES_PATH = "telecom/fav.vcf"; // Contacts marked as favorite
    public static final String MCH_PATH = "telecom/mch.vcf"; // Missed Calls
    public static final String ICH_PATH = "telecom/ich.vcf"; // Incoming Calls
    public static final String OCH_PATH = "telecom/och.vcf"; // Outgoing Calls
    public static final String SIM_PHONEBOOK_PATH = "SIM1/telecom/pb.vcf"; // SIM stored phonebook
    public static final String SIM_MCH_PATH = "SIM1/telecom/mch.vcf"; // SIM stored Missed Calls
    public static final String SIM_ICH_PATH = "SIM1/telecom/ich.vcf"; // SIM stored Incoming Calls
    public static final String SIM_OCH_PATH = "SIM1/telecom/och.vcf"; // SIM stored Outgoing Calls

    // VCard Formats, both are required to be supported by the Server, PBAP 1.2.3, Section 5.1.4.2
    public static byte FORMAT_VCARD_21 = 0;
    public static byte FORMAT_VCARD_30 = 1;

    private final String mPhonebook;
    private final int mListStartOffset;
    private final List<VCardEntry> mCards = new ArrayList<VCardEntry>();
    private final Account mAccount;

    // Needed for VCard parsing, since the account on older platform versions cannot be associated
    // with the VCard (to construct a query) after parse time. Newer platform versions support this
    // though, which means we can eventually remove this in favor of assigning an account post parse
    // time.
    @Nullable private final Account mAccount;

    class CardEntryHandler implements VCardEntryHandler {
        @Override
@@ -56,19 +80,32 @@ class PbapClientVcardList {
        public void onEnd() {}
    }

    PbapClientVcardList(Account account, InputStream in, byte format) throws IOException {
        if (format != PbapClientConnectionHandler.VCARD_TYPE_21
                && format != PbapClientConnectionHandler.VCARD_TYPE_30) {
    PbapPhonebook(String phonebook) {
        mPhonebook = phonebook;
        mAccount = null;
        mListStartOffset = 0;
    }

    PbapPhonebook(
            String phonebook,
            byte format,
            int listStartOffset,
            @Nullable Account account,
            InputStream inputStream)
            throws IOException {
        if (format != FORMAT_VCARD_21 && format != FORMAT_VCARD_30) {
            throw new IllegalArgumentException("Unsupported vCard version.");
        }
        mPhonebook = phonebook;
        mListStartOffset = listStartOffset;
        mAccount = account;
        parse(in, format);
        parse(inputStream, format);
    }

    private void parse(InputStream in, byte format) throws IOException {
        VCardParser parser;

        if (format == PbapClientConnectionHandler.VCARD_TYPE_30) {
        if (format == FORMAT_VCARD_30) {
            parser = new VCardParser_V30();
        } else {
            parser = new VCardParser_V21();
@@ -92,7 +129,7 @@ class PbapClientVcardList {
        // fails with a different {@link VCardException}.
        if (parsedWithVcardVersionException(parser, bufferedInput)) {
            // PBAP v1.2.3 only supports vCard versions 2.1 and 3.0; it's one or the other
            if (format == PbapClientConnectionHandler.VCARD_TYPE_21) {
            if (format == FORMAT_VCARD_21) {
                parser = new VCardParser_V30();
                Log.w(TAG, "vCard version and Parser mismatch; expected v2.1, switching to v3.0");
            } else {
@@ -132,15 +169,46 @@ class PbapClientVcardList {
        return false;
    }

    /**
     * Get the phonebook path associated with this PbapPhonebook object
     *
     * @return a string representing the path these VCard objects were requested from
     */
    public String getPhonebook() {
        return mPhonebook;
    }

    /**
     * Get the offset associated with this PbapPhonebook object
     *
     * <p>The offset respresents the start index of the remote contacts pull
     *
     * @return an int representing the offset index where this pull started from
     */
    public int getOffset() {
        return mListStartOffset;
    }

    /**
     * Get the total number of contacts contained in this phonebook
     *
     * @return an int containing the total number of contacts contained in this phonebook
     */
    public int getCount() {
        return mCards.size();
    }

    /**
     * Get the list of VCard objects contained in this phonebook
     *
     * @return a list of VCard objects in this phonebook
     */
    public List<VCardEntry> getList() {
        return mCards;
    }

    public VCardEntry getFirst() {
        return mCards.get(0);
    @Override
    public String toString() {
        return "<" + TAG + "phonebook=" + mPhonebook + " entries=" + getCount() + ">";
    }
}
Loading