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

Commit d7d41975 authored by Hyundo Moon's avatar Hyundo Moon
Browse files

Add BluetoothPbapUtilsTest

Bug: 237467631
Test: atest BluetoothPbapUtilsTest
Change-Id: I2770665a454e8ba6ff158c99013cbd0d0bc98149
parent 9cf3ccc5
Loading
Loading
Loading
Loading
+42 −24
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import android.provider.ContactsContract.Profile;
import android.provider.ContactsContract.RawContactsEntity;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.vcard.VCardComposer;
import com.android.vcard.VCardConfig;

@@ -68,13 +70,17 @@ class BluetoothPbapUtils {

    static long sPrimaryVersionCounter = 0;
    static long sSecondaryVersionCounter = 0;
    private static long sTotalContacts = 0;
    @VisibleForTesting
    static long sTotalContacts = 0;

    /* totalFields and totalSvcFields used to update primary/secondary version
     * counter between pbap sessions*/
    private static long sTotalFields = 0;
    private static long sTotalSvcFields = 0;
    private static long sContactsLastUpdated = 0;
    @VisibleForTesting
    static long sTotalFields = 0;
    @VisibleForTesting
    static long sTotalSvcFields = 0;
    @VisibleForTesting
    static long sContactsLastUpdated = 0;

    private static class ContactData {
        private String mName;
@@ -97,14 +103,20 @@ class BluetoothPbapUtils {
        }
    }

    private static HashMap<String, ContactData> sContactDataset = new HashMap<>();
    @VisibleForTesting
    static HashMap<String, ContactData> sContactDataset = new HashMap<>();

    private static HashSet<String> sContactSet = new HashSet<>();
    @VisibleForTesting
    static HashSet<String> sContactSet = new HashSet<>();

    private static final String TYPE_NAME = "name";
    private static final String TYPE_PHONE = "phone";
    private static final String TYPE_EMAIL = "email";
    private static final String TYPE_ADDRESS = "address";
    @VisibleForTesting
    static final String TYPE_NAME = "name";
    @VisibleForTesting
    static final String TYPE_PHONE = "phone";
    @VisibleForTesting
    static final String TYPE_EMAIL = "email";
    @VisibleForTesting
    static final String TYPE_ADDRESS = "address";

    private static boolean hasFilter(byte[] filter) {
        return filter != null && filter.length > 0;
@@ -171,8 +183,9 @@ class BluetoothPbapUtils {
    }

    public static String getProfileName(Context context) {
        Cursor c = context.getContentResolver()
                .query(Profile.CONTENT_URI, new String[]{Profile.DISPLAY_NAME}, null, null, null);
        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(
                context.getContentResolver(), Profile.CONTENT_URI,
                new String[]{Profile.DISPLAY_NAME}, null, null, null);
        String ownerName = null;
        if (c != null && c.moveToFirst()) {
            ownerName = c.getString(0);
@@ -267,8 +280,8 @@ class BluetoothPbapUtils {
        HashSet<String> currentContactSet = new HashSet<>();

        String[] projection = {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP};
        Cursor c = context.getContentResolver()
                .query(Contacts.CONTENT_URI, projection, null, null, null);
        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(
                context.getContentResolver(), Contacts.CONTENT_URI, projection, null, null, null);

        if (c == null) {
            Log.d(TAG, "Failed to fetch data from contact database");
@@ -320,8 +333,9 @@ class BluetoothPbapUtils {
            for (String deletedContact : deletedContacts) {
                sContactSet.remove(deletedContact);
                String[] selectionArgs = {deletedContact};
                Cursor dataCursor = context.getContentResolver()
                        .query(Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
                Cursor dataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
                        context.getContentResolver(), Data.CONTENT_URI, dataProjection, whereClause,
                        selectionArgs, null);

                if (dataCursor == null) {
                    Log.d(TAG, "Failed to fetch data from contact database");
@@ -350,8 +364,9 @@ class BluetoothPbapUtils {
                boolean updated = false;

                String[] selectionArgs = {contact};
                Cursor dataCursor = context.getContentResolver()
                        .query(Data.CONTENT_URI, dataProjection, whereClause, selectionArgs, null);
                Cursor dataCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
                        context.getContentResolver(), Data.CONTENT_URI, dataProjection, whereClause,
                        selectionArgs, null);

                if (dataCursor == null) {
                    Log.d(TAG, "Failed to fetch data from contact database");
@@ -419,7 +434,8 @@ class BluetoothPbapUtils {
    /* checkFieldUpdates checks update contact fields of a particular contact.
     * Field update can be a field updated/added/deleted in an existing contact.
     * Returns true if any contact field is updated else return false. */
    private static boolean checkFieldUpdates(ArrayList<String> oldFields,
    @VisibleForTesting
    static boolean checkFieldUpdates(ArrayList<String> oldFields,
            ArrayList<String> newFields) {
        if (newFields != null && oldFields != null) {
            if (newFields.size() != oldFields.size()) {
@@ -451,11 +467,13 @@ class BluetoothPbapUtils {
    /* fetchAndSetContacts reads contacts and caches them
     * isLoad = true indicates its loading all contacts
     * isLoad = false indiacates its caching recently added contact in database*/
    private static int fetchAndSetContacts(Context context, Handler handler, String[] projection,
    @VisibleForTesting
    static int fetchAndSetContacts(Context context, Handler handler, String[] projection,
            String whereClause, String[] selectionArgs, boolean isLoad) {
        long currentTotalFields = 0, currentSvcFieldCount = 0;
        Cursor c = context.getContentResolver()
                .query(Data.CONTENT_URI, projection, whereClause, selectionArgs, null);
        Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(
                context.getContentResolver(), Data.CONTENT_URI, projection, whereClause,
                selectionArgs, null);

        /* send delayed message to loadContact when ContentResolver is unable
         * to fetch data from contact database using the specified URI at that
@@ -540,8 +558,8 @@ class BluetoothPbapUtils {
     * email or address which is required for updating Secondary Version counter).
     * contactsFieldData - List of field data for phone/email/address.
     * contactId - Contact ID, data1 - field value from data table for phone/email/address*/

    private static void setContactFields(String fieldType, String contactId, String data) {
    @VisibleForTesting
    static void setContactFields(String fieldType, String contactId, String data) {
        ContactData cData;
        if (sContactDataset.containsKey(contactId)) {
            cData = sContactDataset.get(contactId);
+354 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 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.pbap;

import static android.provider.ContactsContract.Data.CONTACT_ID;
import static android.provider.ContactsContract.Data.DATA1;
import static android.provider.ContactsContract.Data.MIMETYPE;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.res.Resources;
import android.database.MatrixCursor;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.vcard.VCardConfig;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

@SmallTest
@RunWith(AndroidJUnit4.class)
public class BluetoothPbapUtilsTest {

    @Mock
    Context mContext;

    @Mock
    Resources mResources;

    @Spy
    BluetoothMethodProxy mProxy;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        BluetoothMethodProxy.setInstanceForTesting(mProxy);

        when(mContext.getResources()).thenReturn(mResources);
        initStaticFields();
    }

    @After
    public void tearDown() {
        BluetoothMethodProxy.setInstanceForTesting(null);
        initStaticFields();
    }

    @Test
    public void checkFieldUpdates_whenSizeAreDifferent_returnsTrue() {
        ArrayList<String> oldFields = new ArrayList<>(List.of("0", "1", "2", "3"));
        ArrayList<String> newFields = new ArrayList<>(List.of("0", "1", "2", "3", "4"));

        assertThat(BluetoothPbapUtils.checkFieldUpdates(oldFields, newFields)).isTrue();
    }

    @Test
    public void checkFieldUpdates_newFieldsHasItsOwnFields_returnsTrue() {
        ArrayList<String> oldFields = new ArrayList<>(List.of("0", "1", "2", "3"));
        ArrayList<String> newFields = new ArrayList<>(List.of("0", "1", "2", "5"));

        assertThat(BluetoothPbapUtils.checkFieldUpdates(oldFields, newFields)).isTrue();
    }

    @Test
    public void checkFieldUpdates_onlyNewFieldsIsNull_returnsTrue() {
        ArrayList<String> oldFields = new ArrayList<>(List.of("0", "1", "2", "3"));
        ArrayList<String> newFields = null;

        assertThat(BluetoothPbapUtils.checkFieldUpdates(oldFields, newFields)).isTrue();
    }

    @Test
    public void checkFieldUpdates_onlyOldFieldsIsNull_returnsTrue() {
        ArrayList<String> oldFields = null;
        ArrayList<String> newFields = new ArrayList<>(List.of("0", "1", "2", "3"));

        assertThat(BluetoothPbapUtils.checkFieldUpdates(oldFields, newFields)).isTrue();
    }

    @Test
    public void checkFieldUpdates_whenBothAreNull_returnsTrue() {
        ArrayList<String> oldFields = null;
        ArrayList<String> newFields = null;

        assertThat(BluetoothPbapUtils.checkFieldUpdates(oldFields, newFields)).isFalse();
    }

    @Test
    public void createFilteredVCardComposer_returnsNewVCardComposer() {
        byte[] filter = new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
                (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
        int vcardType = VCardConfig.VCARD_TYPE_V21_GENERIC;

        assertThat(BluetoothPbapUtils.createFilteredVCardComposer(mContext, vcardType, filter))
                .isNotNull();
    }

    @Test
    public void rolloverCounters() {
        BluetoothPbapUtils.sPrimaryVersionCounter = -1;
        BluetoothPbapUtils.sSecondaryVersionCounter = -1;

        BluetoothPbapUtils.rolloverCounters();

        assertThat(BluetoothPbapUtils.sPrimaryVersionCounter).isEqualTo(0);
        assertThat(BluetoothPbapUtils.sSecondaryVersionCounter).isEqualTo(0);
    }

    @Test
    public void setContactFields() {
        String contactId = "1358923";

        BluetoothPbapUtils.setContactFields(BluetoothPbapUtils.TYPE_NAME, contactId,
                "test_name");
        BluetoothPbapUtils.setContactFields(BluetoothPbapUtils.TYPE_PHONE, contactId,
                "0123456789");
        BluetoothPbapUtils.setContactFields(BluetoothPbapUtils.TYPE_EMAIL, contactId,
                "android@android.com");
        BluetoothPbapUtils.setContactFields(BluetoothPbapUtils.TYPE_ADDRESS, contactId,
                "SomeAddress");

        assertThat(BluetoothPbapUtils.sContactDataset.get(contactId)).isNotNull();
    }

    @Test
    public void fetchAndSetContacts_whenCursorIsNull_returnsMinusOne() {
        doReturn(null).when(mProxy).contentResolverQuery(
                any(), any(), any(), any(), any(), any());
        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            assertThat(BluetoothPbapUtils.fetchAndSetContacts(
                    mContext, handler, null, null, null, true))
                    .isEqualTo(-1);
        } finally {
            handlerThread.quit();
        }
    }

    @Test
    public void fetchAndSetContacts_whenIsLoadTrue_returnsContactsSetSize() {
        MatrixCursor cursor = new MatrixCursor(new String[] {CONTACT_ID, MIMETYPE, DATA1});
        cursor.addRow(new Object[] {"id1", Phone.CONTENT_ITEM_TYPE, "01234567"});
        cursor.addRow(new Object[] {"id1", Email.CONTENT_ITEM_TYPE, "android@android.com"});
        cursor.addRow(new Object[] {"id1", StructuredPostal.CONTENT_ITEM_TYPE, "01234"});
        cursor.addRow(new Object[] {"id2", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
        cursor.addRow(new Object[] {null, null, null});

        doReturn(cursor).when(mProxy).contentResolverQuery(
                any(), any(), any(), any(), any(), any());
        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            boolean isLoad = true;
            assertThat(BluetoothPbapUtils.fetchAndSetContacts(
                    mContext, handler, null, null, null, isLoad))
                    .isEqualTo(2); // Two IDs exist in sContactSet.
        } finally {
            handlerThread.quit();
        }
    }

    @Test
    public void fetchAndSetContacts_whenIsLoadFalse_returnsContactsSetSize() {
        MatrixCursor cursor = new MatrixCursor(new String[] {CONTACT_ID, MIMETYPE, DATA1});
        cursor.addRow(new Object[] {"id1", Phone.CONTENT_ITEM_TYPE, "01234567"});
        cursor.addRow(new Object[] {"id1", Email.CONTENT_ITEM_TYPE, "android@android.com"});
        cursor.addRow(new Object[] {"id1", StructuredPostal.CONTENT_ITEM_TYPE, "01234"});
        cursor.addRow(new Object[] {"id2", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
        cursor.addRow(new Object[] {null, null, null});

        doReturn(cursor).when(mProxy).contentResolverQuery(
                any(), any(), any(), any(), any(), any());
        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            boolean isLoad = false;
            assertThat(BluetoothPbapUtils.fetchAndSetContacts(
                    mContext, handler, null, null, null, isLoad))
                    .isEqualTo(2); // Two IDs exist in sContactSet.
            assertThat(BluetoothPbapUtils.sTotalFields).isEqualTo(1);
            assertThat(BluetoothPbapUtils.sTotalSvcFields).isEqualTo(1);
        } finally {
            handlerThread.quit();
        }
    }

    @Test
    public void updateSecondaryVersionCounter_whenCursorIsNull_shouldNotCrash() {
        doReturn(null).when(mProxy).contentResolverQuery(
                any(), any(), any(), any(), any(), any());
        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, handler);
        } catch (Exception e) {
            assertWithMessage("Exception should not happen.").fail();
        } finally {
            handlerThread.quit();
        }
    }

    @Test
    public void updateSecondaryVersionCounter_whenContactsAreAdded() {
        MatrixCursor contactCursor = new MatrixCursor(
                new String[] {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP});
        contactCursor.addRow(new Object[] {"id1", Calendar.getInstance().getTimeInMillis()});
        contactCursor.addRow(new Object[] {"id2", Calendar.getInstance().getTimeInMillis()});
        contactCursor.addRow(new Object[] {"id3", Calendar.getInstance().getTimeInMillis()});
        contactCursor.addRow(new Object[] {"id4", Calendar.getInstance().getTimeInMillis()});
        doReturn(contactCursor).when(mProxy).contentResolverQuery(
                any(), eq(Contacts.CONTENT_URI), any(), any(), any(), any());

        MatrixCursor dataCursor = new MatrixCursor(new String[] {CONTACT_ID, MIMETYPE, DATA1});
        dataCursor.addRow(new Object[] {"id1", Phone.CONTENT_ITEM_TYPE, "01234567"});
        dataCursor.addRow(new Object[] {"id1", Email.CONTENT_ITEM_TYPE, "android@android.com"});
        dataCursor.addRow(new Object[] {"id1", StructuredPostal.CONTENT_ITEM_TYPE, "01234"});
        dataCursor.addRow(new Object[] {"id2", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
        doReturn(dataCursor).when(mProxy).contentResolverQuery(
                any(), eq(Data.CONTENT_URI), any(), any(), any(), any());

        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, handler);

            assertThat(BluetoothPbapUtils.sTotalContacts).isEqualTo(4);
        } finally {
            handlerThread.quit();
        }
    }

    @Test
    public void updateSecondaryVersionCounter_whenContactsAreDeleted() {
        MatrixCursor contactCursor = new MatrixCursor(
                new String[] {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP});
        doReturn(contactCursor).when(mProxy).contentResolverQuery(
                any(), eq(Contacts.CONTENT_URI), any(), any(), any(), any());

        MatrixCursor dataCursor = new MatrixCursor(new String[] {CONTACT_ID, MIMETYPE, DATA1});
        doReturn(dataCursor).when(mProxy).contentResolverQuery(
                any(), eq(Data.CONTENT_URI), any(), any(), any(), any());

        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            BluetoothPbapUtils.sTotalContacts = 2;
            BluetoothPbapUtils.sContactSet.add("id1");
            BluetoothPbapUtils.sContactSet.add("id2");

            BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, handler);

            assertThat(BluetoothPbapUtils.sTotalContacts).isEqualTo(0);
        } finally {
            handlerThread.quit();
        }
    }

    @Test
    public void updateSecondaryVersionCounter_whenContactsAreUpdated() {
        MatrixCursor contactCursor = new MatrixCursor(
                new String[] {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP});
        contactCursor.addRow(new Object[] {"id1", Calendar.getInstance().getTimeInMillis()});
        contactCursor.addRow(new Object[] {"id2", Calendar.getInstance().getTimeInMillis()});
        contactCursor.addRow(new Object[] {"id3", Calendar.getInstance().getTimeInMillis()});
        contactCursor.addRow(new Object[] {"id4", Calendar.getInstance().getTimeInMillis()});
        doReturn(contactCursor).when(mProxy).contentResolverQuery(
                any(), eq(Contacts.CONTENT_URI), any(), any(), any(), any());

        MatrixCursor dataCursor = new MatrixCursor(new String[] {CONTACT_ID, MIMETYPE, DATA1});
        dataCursor.addRow(new Object[] {"id1", Phone.CONTENT_ITEM_TYPE, "01234567"});
        dataCursor.addRow(new Object[] {"id1", Email.CONTENT_ITEM_TYPE, "android@android.com"});
        dataCursor.addRow(new Object[] {"id1", StructuredPostal.CONTENT_ITEM_TYPE, "01234"});
        dataCursor.addRow(new Object[] {"id2", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
        doReturn(dataCursor).when(mProxy).contentResolverQuery(
                any(), eq(Data.CONTENT_URI), any(), any(), any(), any());

        HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        try {
            BluetoothPbapUtils.sTotalContacts = 4;

            BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, handler);

            assertThat(BluetoothPbapUtils.sContactDataset).isNotEmpty();
        } finally {
            handlerThread.quit();
        }
    }

    private static void initStaticFields() {
        BluetoothPbapUtils.sPrimaryVersionCounter = 0;
        BluetoothPbapUtils.sSecondaryVersionCounter = 0;
        BluetoothPbapUtils.sContactSet.clear();
        BluetoothPbapUtils.sTotalContacts = 0;
        BluetoothPbapUtils.sTotalFields = 0;
        BluetoothPbapUtils.sTotalSvcFields = 0;
    }
}