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

Commit 694a060b authored by Hieu Dang's avatar Hieu Dang Committed by Gerrit Code Review
Browse files

Merge changes I0779eb81,Ieb8129bf,I9f821fb1

* changes:
  Add more tests for BluetoothOppService
  Fix IllegalArgumentException in BluetoothOppServiceTest
  Revert "OPP: Do not trimDatabase if ContentProvider doesn't exist"
parents 9dba7619 6903f9bd
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -258,4 +258,9 @@ public class BluetoothMethodProxy {
            ContextMap map, GattService service) {
        return new AppAdvertiseStats(appUid, id, name, map, service);
    }

    /** Proxies {@link Thread#start()}. */
    public void threadStart(Thread thread) {
        thread.start();
    }
}
+75 −44
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import android.os.Process;
import android.sysprop.BluetoothProperties;
import android.util.Log;

import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
@@ -130,13 +131,12 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti

    private boolean mPendingUpdate;

    private UpdateThread mUpdateThread;
    @VisibleForTesting UpdateThread mUpdateThread;

    private boolean mUpdateThreadRunning;

    private ArrayList<BluetoothOppShareInfo> mShares;

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

    private BluetoothOppTransfer mTransfer;

@@ -180,9 +180,17 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
                    + BluetoothShare.USER_CONFIRMATION + "="
                    + BluetoothShare.USER_CONFIRMATION_PENDING;

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

    private static BluetoothOppService sBluetoothOppService;

@@ -609,7 +617,7 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
            mPendingUpdate = true;
            if (mUpdateThread == null) {
                mUpdateThread = new UpdateThread();
                mUpdateThread.start();
                BluetoothMethodProxy.getInstance().threadStart(mUpdateThread);
                mUpdateThreadRunning = true;
            }
        }
@@ -985,10 +993,9 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
        }
    }

    /**
     * Removes the local copy of the info about a share.
     */
    private void deleteShare(int arrayPos) {
    /** Removes the local copy of the info about a share. */
    @VisibleForTesting
    void deleteShare(int arrayPos) {
        BluetoothOppShareInfo info = mShares.get(arrayPos);

        /*
@@ -1112,14 +1119,19 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
    }

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

    @VisibleForTesting
    static void trimDatabase(ContentResolver contentResolver) {
        // Try-catch is important because trimDatabase can run even when the OPP_PROVIDER is
        // disabled (by OPP service, shell command, etc.).
        // At the sametime, it's ok to retry trimDatabase later when the service restart
        try {
            // remove the invisible/unconfirmed inbound shares
        int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
            int delNum =
                    BluetoothMethodProxy.getInstance()
                            .contentResolverDelete(
                                    contentResolver,
                                    BluetoothShare.CONTENT_URI,
                                    WHERE_INVISIBLE_UNCONFIRMED,
                                    null);
            if (V) {
                Log.v(TAG, "Deleted shares, number = " + delNum);
@@ -1127,8 +1139,14 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti

            // Keep the latest inbound and successful shares.
            Cursor cursor =
                contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
                        WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
                    BluetoothMethodProxy.getInstance()
                            .contentResolverQuery(
                                    contentResolver,
                                    BluetoothShare.CONTENT_URI,
                                    new String[] {BluetoothShare._ID},
                                    WHERE_INBOUND_SUCCESS,
                                    null,
                                    BluetoothShare._ID); // sort by id
            if (cursor == null) {
                return;
            }
@@ -1139,14 +1157,22 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
                if (cursor.moveToPosition(numToDelete)) {
                    int columnId = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
                    long id = cursor.getLong(columnId);
                delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
                        BluetoothShare._ID + " < " + id, null);
                    delNum =
                            BluetoothMethodProxy.getInstance()
                                    .contentResolverDelete(
                                            contentResolver,
                                            BluetoothShare.CONTENT_URI,
                                            BluetoothShare._ID + " < " + id,
                                            null);
                    if (V) {
                        Log.v(TAG, "Deleted old inbound success share: " + delNum);
                    }
                }
            }
            cursor.close();
        } catch (Exception e) {
            Log.e(TAG, "Exception when trimming database: ", e);
        }
    }

    private static class MediaScannerNotifier implements MediaScannerConnectionClient {
@@ -1234,7 +1260,12 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti
    public boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {

        if (D) {
            Log.d(TAG, " onConnect BluetoothSocket :" + socket + " \n :device :" + device.getIdentityAddress());
            Log.d(
                    TAG,
                    " onConnect BluetoothSocket :"
                            + socket
                            + " \n :device :"
                            + device.getIdentityAddress());
        }
        if (!mAcceptNewConnections) {
            Log.d(TAG, " onConnect BluetoothSocket :" + socket + " rejected");
+120 −7
Original line number Diff line number Diff line
@@ -15,23 +15,33 @@
 */
package com.android.bluetooth.opp;

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

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

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

import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.ContentResolver;
import android.database.MatrixCursor;

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

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

import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -39,25 +49,36 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;


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

    @Rule
    public final ServiceTestRule mServiceRule = new ServiceTestRule();
    @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();

    @Mock BluetoothMethodProxy mBluetoothMethodProxy;

    @Mock
    private AdapterService mAdapterService;
    @Mock private AdapterService mAdapterService;

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

        BluetoothMethodProxy.setInstanceForTesting(mBluetoothMethodProxy);
        // BluetoothOppService can create a UpdateThread, which will call
        // BluetoothOppNotification#updateNotification(), which in turn create a new
        // NotificationUpdateThread. Both threads may cause the tests to fail because they try to
        // access to ContentProvider in multiple places (ContentProvider might be disabled & there
        // is no mocking). Since we have no intention to test those threads, avoid running them
        doNothing().when(mBluetoothMethodProxy).threadStart(any());

        TestUtils.setAdapterService(mAdapterService);
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        TestUtils.startService(mServiceRule, BluetoothOppService.class);
        mService = BluetoothOppService.getBluetoothOppService();

        Assert.assertNotNull(mService);
        // Try getting the Bluetooth adapter
        mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -66,6 +87,11 @@ public class BluetoothOppServiceTest {

    @After
    public void tearDown() throws Exception {
        // Since the update thread is not run (we mocked it), it will not clean itself on interrupt
        // (normally, the service will wait for the update thread to clean itself after
        // being interrupted). We clean it manually here
        mService.mUpdateThread = null;
        BluetoothMethodProxy.setInstanceForTesting(null);
        TestUtils.stopService(mServiceRule, BluetoothOppService.class);
        TestUtils.clearAdapterService(mAdapterService);
    }
@@ -74,5 +100,92 @@ public class BluetoothOppServiceTest {
    public void testInitialize() {
        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();

        doReturn(1 /* any int is Ok */)
                .when(mBluetoothMethodProxy)
                .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(mBluetoothMethodProxy)
                .contentResolverQuery(
                        eq(contentResolver),
                        eq(BluetoothShare.CONTENT_URI),
                        any(),
                        any(),
                        any(),
                        any());

        BluetoothOppService.trimDatabase(contentResolver);

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

        // check trimmed old records
        verify(mBluetoothMethodProxy)
                .contentResolverDelete(
                        eq(contentResolver),
                        eq(BluetoothShare.CONTENT_URI),
                        eq(BluetoothShare._ID + " < " + 20),
                        any());
    }
}