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

Commit aa0cdc63 authored by My Name's avatar My Name
Browse files

Add test & fix bug for BluetoothOppServiceTest

Add test for BluetoothOppService#trimDatabase &
fixing b/262201478

Test: atest BluetoothInstrumentationTests
Bug: 237467631
Bug: 262201478
Tag: #refactor
Change-Id: I32a84781d8eb4e1893f627b583920b0ef346f9da
(cherry picked from commit 7bd6f7bd)
Merged-In: I32a84781d8eb4e1893f627b583920b0ef346f9da
parent ddbd2016
Loading
Loading
Loading
Loading
+20 −0
Original line number Original line Diff line number Diff line
@@ -16,12 +16,14 @@


package com.android.bluetooth;
package com.android.bluetooth;


import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.PeriodicAdvertisingCallback;
import android.bluetooth.le.PeriodicAdvertisingCallback;
import android.bluetooth.le.PeriodicAdvertisingManager;
import android.bluetooth.le.PeriodicAdvertisingManager;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanResult;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.ContentValues;
import android.content.Context;
import android.content.Context;
@@ -39,6 +41,7 @@ import android.util.Log;
import com.android.bluetooth.gatt.AppAdvertiseStats;
import com.android.bluetooth.gatt.AppAdvertiseStats;
import com.android.bluetooth.gatt.ContextMap;
import com.android.bluetooth.gatt.ContextMap;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.opp.BluetoothOppNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.HeaderSet;
import com.android.obex.HeaderSet;


@@ -161,6 +164,14 @@ public class BluetoothMethodProxy {
        return contentResolver.openInputStream(uri);
        return contentResolver.openInputStream(uri);
    }
    }


    /**
     * Proxies {@link ContentResolver#acquireUnstableContentProviderClient(String)}.
     */
    public ContentProviderClient contentResolverAcquireUnstableContentProviderClient(
            ContentResolver contentResolver, @NonNull String name) {
        return contentResolver.acquireUnstableContentProviderClient(name);
    }

    /**
    /**
     * Proxies {@link Context#sendBroadcast(Intent)}.
     * Proxies {@link Context#sendBroadcast(Intent)}.
     */
     */
@@ -223,4 +234,13 @@ public class BluetoothMethodProxy {
            ContextMap map, GattService service) {
            ContextMap map, GattService service) {
        return new AppAdvertiseStats(appUid, id, name, map, service);
        return new AppAdvertiseStats(appUid, id, name, map, service);
    }
    }


    /**
     * Proxies {@link com.android.bluetooth.opp.BluetoothOppNotification#BluetoothOppNotification(
     * Context)}.
     */
    public BluetoothOppNotification newBluetoothOppNotification(final Context context) {
        return new BluetoothOppNotification(context);
    }
}
}
+2 −2
Original line number Original line Diff line number Diff line
@@ -61,7 +61,7 @@ import java.util.HashMap;
 * where there is an ongoing transfer, incoming transfer need confirm and
 * where there is an ongoing transfer, incoming transfer need confirm and
 * complete (successful or failed) transfer.
 * complete (successful or failed) transfer.
 */
 */
class BluetoothOppNotification {
public class BluetoothOppNotification {
    private static final String TAG = "BluetoothOppNotification";
    private static final String TAG = "BluetoothOppNotification";
    private static final boolean V = Constants.VERBOSE;
    private static final boolean V = Constants.VERBOSE;


@@ -152,7 +152,7 @@ class BluetoothOppNotification {
     * @param ctx The context to use to obtain access to the Notification
     * @param ctx The context to use to obtain access to the Notification
     *            Service
     *            Service
     */
     */
    BluetoothOppNotification(Context ctx) {
    public BluetoothOppNotification(Context ctx) {
        mContext = ctx;
        mContext = ctx;
        mNotificationMgr = mContext.getSystemService(NotificationManager.class);
        mNotificationMgr = mContext.getSystemService(NotificationManager.class);
        mNotificationChannel = new NotificationChannel(OPP_NOTIFICATION_CHANNEL,
        mNotificationChannel = new NotificationChannel(OPP_NOTIFICATION_CHANNEL,
+27 −14
Original line number Original line Diff line number Diff line
@@ -55,6 +55,7 @@ import android.os.Process;
import android.sysprop.BluetoothProperties;
import android.sysprop.BluetoothProperties;
import android.util.Log;
import android.util.Log;


import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
import com.android.bluetooth.ObexServerSockets;
@@ -128,7 +129,8 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
    private BluetoothShareContentObserver mObserver;
    private BluetoothShareContentObserver mObserver;


    /** Class to handle Notification Manager updates */
    /** Class to handle Notification Manager updates */
    private BluetoothOppNotification mNotifier;
    @VisibleForTesting
    BluetoothOppNotification mNotifier;


    private boolean mPendingUpdate;
    private boolean mPendingUpdate;


@@ -136,9 +138,11 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti


    private boolean mUpdateThreadRunning;
    private boolean mUpdateThreadRunning;


    private ArrayList<BluetoothOppShareInfo> mShares;
    @VisibleForTesting
    ArrayList<BluetoothOppShareInfo> mShares;


    private ArrayList<BluetoothOppBatch> mBatches;
    @VisibleForTesting
    ArrayList<BluetoothOppBatch> mBatches;


    private BluetoothOppTransfer mTransfer;
    private BluetoothOppTransfer mTransfer;


@@ -182,7 +186,8 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
                    + BluetoothShare.USER_CONFIRMATION + "="
                    + BluetoothShare.USER_CONFIRMATION + "="
                    + BluetoothShare.USER_CONFIRMATION_PENDING;
                    + BluetoothShare.USER_CONFIRMATION_PENDING;


    private static final String WHERE_INVISIBLE_UNCONFIRMED =
    @VisibleForTesting
    static final String WHERE_INVISIBLE_UNCONFIRMED =
            "(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND "
            "(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND "
                    + INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
                    + INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";


@@ -261,7 +266,7 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
        mAdapterService = AdapterService.getAdapterService();
        mAdapterService = AdapterService.getAdapterService();
        mObserver = new BluetoothShareContentObserver();
        mObserver = new BluetoothShareContentObserver();
        getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
        getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
        mNotifier = new BluetoothOppNotification(this);
        mNotifier = BluetoothMethodProxy.getInstance().newBluetoothOppNotification(this);
        mNotifier.mNotificationMgr.cancelAll();
        mNotifier.mNotificationMgr.cancelAll();
        mNotifier.updateNotification();
        mNotifier.updateNotification();
        updateFromProvider();
        updateFromProvider();
@@ -990,7 +995,8 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
    /**
    /**
     * Removes the local copy of the info about a share.
     * Removes the local copy of the info about a share.
     */
     */
    private void deleteShare(int arrayPos) {
    @VisibleForTesting
    void deleteShare(int arrayPos) {
        BluetoothOppShareInfo info = mShares.get(arrayPos);
        BluetoothOppShareInfo info = mShares.get(arrayPos);


        /*
        /*
@@ -1114,17 +1120,24 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
    }
    }


    // Run in a background thread at boot.
    // Run in a background thread at boot.
    private static void trimDatabase(ContentResolver contentResolver) {
    @VisibleForTesting
    static void trimDatabase(ContentResolver contentResolver) {
        if (contentResolver.acquireContentProviderClient(BluetoothShare.CONTENT_URI) == null) {
            Log.w(TAG, "ContentProvider doesn't exist");
            return;
        }

        // remove the invisible/unconfirmed inbound shares
        // remove the invisible/unconfirmed inbound shares
        int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
        int delNum = BluetoothMethodProxy.getInstance().contentResolverDelete(
                null);
                contentResolver, BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED, null);

        if (V) {
        if (V) {
            Log.v(TAG, "Deleted shares, number = " + delNum);
            Log.v(TAG, "Deleted shares, number = " + delNum);
        }
        }


        // Keep the latest inbound and successful shares.
        // Keep the latest inbound and successful shares.
        Cursor cursor =
        Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
                contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
                contentResolver, BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
                WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
                WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
        if (cursor == null) {
        if (cursor == null) {
            return;
            return;
@@ -1136,8 +1149,8 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
            if (cursor.moveToPosition(numToDelete)) {
            if (cursor.moveToPosition(numToDelete)) {
                int columnId = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
                int columnId = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
                long id = cursor.getLong(columnId);
                long id = cursor.getLong(columnId);
                delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
                delNum = BluetoothMethodProxy.getInstance().contentResolverDelete(contentResolver,
                        BluetoothShare._ID + " < " + id, null);
                        BluetoothShare.CONTENT_URI, BluetoothShare._ID + " < " + id, null);
                if (V) {
                if (V) {
                    Log.v(TAG, "Deleted old inbound success share: " + delNum);
                    Log.v(TAG, "Deleted old inbound success share: " + delNum);
                }
                }
+100 −8
Original line number Original line Diff line number Diff line
@@ -15,17 +15,28 @@
 */
 */
package com.android.bluetooth.opp;
package com.android.bluetooth.opp;


import static com.android.bluetooth.opp.BluetoothOppService.WHERE_INVISIBLE_UNCONFIRMED;

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

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;


import android.app.NotificationManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.ContentResolver;
import android.database.MatrixCursor;
import android.os.Handler;


import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ServiceTestRule;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.runner.AndroidJUnit4;


import com.android.bluetooth.R;
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.AdapterService;


@@ -39,14 +50,14 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoAnnotations;


@MediumTest
@RunWith(AndroidJUnit4.class)
@RunWith(AndroidJUnit4.class)
public class BluetoothOppServiceTest {
public class BluetoothOppServiceTest {
    private BluetoothOppService mService = null;
    private BluetoothAdapter mAdapter = null;

    @Rule
    @Rule
    public final ServiceTestRule mServiceRule = new ServiceTestRule();
    public final ServiceTestRule mServiceRule = new ServiceTestRule();
    @Mock
    BluetoothMethodProxy mMethodProxy;
    private BluetoothOppService mService = null;
    private BluetoothAdapter mAdapter = null;


    @Mock
    @Mock
    private AdapterService mAdapterService;
    private AdapterService mAdapterService;
@@ -56,8 +67,18 @@ public class BluetoothOppServiceTest {
        Assume.assumeTrue("Ignore test when BluetoothOppService is not enabled",
        Assume.assumeTrue("Ignore test when BluetoothOppService is not enabled",
                BluetoothOppService.isEnabled());
                BluetoothOppService.isEnabled());
        MockitoAnnotations.initMocks(this);
        MockitoAnnotations.initMocks(this);

        BluetoothMethodProxy.setInstanceForTesting(mMethodProxy);

        // To void mockito multi-thread inter-tests problem
        // If the thread still run in the next test, it will raise un-related mockito error
        BluetoothOppNotification bluetoothOppNotification = mock(BluetoothOppNotification.class);
        bluetoothOppNotification.mNotificationMgr = mock(NotificationManager.class);
        doReturn(bluetoothOppNotification).when(mMethodProxy).newBluetoothOppNotification(any());

        TestUtils.setAdapterService(mAdapterService);
        TestUtils.setAdapterService(mAdapterService);
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());

        TestUtils.startService(mServiceRule, BluetoothOppService.class);
        TestUtils.startService(mServiceRule, BluetoothOppService.class);
        mService = BluetoothOppService.getBluetoothOppService();
        mService = BluetoothOppService.getBluetoothOppService();
        Assert.assertNotNull(mService);
        Assert.assertNotNull(mService);
@@ -68,6 +89,7 @@ public class BluetoothOppServiceTest {


    @After
    @After
    public void tearDown() throws Exception {
    public void tearDown() throws Exception {
        BluetoothMethodProxy.setInstanceForTesting(null);
        if (!BluetoothOppService.isEnabled()) {
        if (!BluetoothOppService.isEnabled()) {
            return;
            return;
        }
        }
@@ -79,4 +101,74 @@ public class BluetoothOppServiceTest {
    public void testInitialize() {
    public void testInitialize() {
        Assert.assertNotNull(BluetoothOppService.getBluetoothOppService());
        Assert.assertNotNull(BluetoothOppService.getBluetoothOppService());
    }
    }

    @Test
    public void deleteShare_deleteShareAndCorrespondingBatch() {
        int infoTimestamp = 123456789;
        int infoTimestamp2 = 123489;

        BluetoothOppShareInfo shareInfo = mock(BluetoothOppShareInfo.class);
        shareInfo.mTimestamp = infoTimestamp;
        shareInfo.mDestination = "AA:BB:CC:DD:EE:FF";
        BluetoothOppShareInfo shareInfo2 = mock(BluetoothOppShareInfo.class);
        shareInfo2.mTimestamp = infoTimestamp2;
        shareInfo2.mDestination = "00:11:22:33:44:55";

        mService.mShares.clear();
        mService.mShares.add(shareInfo);
        mService.mShares.add(shareInfo2);

        // batch1 will be removed
        BluetoothOppBatch batch1 = new BluetoothOppBatch(mService, shareInfo);
        BluetoothOppBatch batch2 = new BluetoothOppBatch(mService, shareInfo2);
        batch2.mStatus = Constants.BATCH_STATUS_FINISHED;
        mService.mBatches.clear();
        mService.mBatches.add(batch1);
        mService.mBatches.add(batch2);

        mService.deleteShare(0);
        assertThat(mService.mShares.size()).isEqualTo(1);
        assertThat(mService.mBatches.size()).isEqualTo(1);
        assertThat(mService.mShares.get(0)).isEqualTo(shareInfo2);
        assertThat(mService.mBatches.get(0)).isEqualTo(batch2);
    }

    @Test
    public void dump_shouldNotThrow() {
        BluetoothOppShareInfo info = mock(BluetoothOppShareInfo.class);

        mService.mShares.add(info);

        // should not throw
        mService.dump(new StringBuilder());
    }

    @Test
    public void trimDatabase_trimsOldOrInvisibleRecords() {
        ContentResolver contentResolver = InstrumentationRegistry
                .getInstrumentation().getTargetContext().getContentResolver();
        Assume.assumeTrue("Ignore test when there is no content provider",
                contentResolver.acquireContentProviderClient(BluetoothShare.CONTENT_URI) != null);

        doReturn(1 /* any int is Ok */).when(mMethodProxy).contentResolverDelete(
                eq(contentResolver), eq(BluetoothShare.CONTENT_URI), anyString(), any());

        MatrixCursor cursor = new MatrixCursor(new String[]{BluetoothShare._ID}, 500);
        for (long i = 0; i < Constants.MAX_RECORDS_IN_DATABASE + 20; i++) {
            cursor.addRow(new Object[]{i});
        }

        doReturn(cursor).when(mMethodProxy).contentResolverQuery(eq(contentResolver),
                eq(BluetoothShare.CONTENT_URI), any(), any(), any(), any());

        BluetoothOppService.trimDatabase(contentResolver);

        // check trimmed invisible records
        verify(mMethodProxy).contentResolverDelete(eq(contentResolver),
                eq(BluetoothShare.CONTENT_URI), eq(WHERE_INVISIBLE_UNCONFIRMED), any());

        // check trimmed old records
        verify(mMethodProxy).contentResolverDelete(eq(contentResolver),
                eq(BluetoothShare.CONTENT_URI), eq(BluetoothShare._ID + " < " + 20), any());
    }
}
}
 No newline at end of file