Loading android/app/src/com/android/bluetooth/opp/BluetoothOppService.java +11 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -184,7 +185,8 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti + BluetoothShare.USER_CONFIRMATION + "=" + BluetoothShare.USER_CONFIRMATION_PENDING; private static final String WHERE_INVISIBLE_UNCONFIRMED = @VisibleForTesting static final String WHERE_INVISIBLE_UNCONFIRMED = "(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")"; Loading Loading @@ -1116,22 +1118,23 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti } // 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 int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED, null); int delNum = BluetoothMethodProxy.getInstance().contentResolverDelete( contentResolver, BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED, null); if (V) { Log.v(TAG, "Deleted shares, number = " + delNum); } // Keep the latest inbound and successful shares. Cursor cursor = contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID}, Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery( contentResolver, BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID}, WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id if (cursor == null) { return; Loading @@ -1143,8 +1146,8 @@ 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); } Loading android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java +46 −6 Original line number Diff line number Diff line Loading @@ -15,19 +15,26 @@ */ 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.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.net.Uri; 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.BluetoothMethodProxy; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; Loading @@ -43,18 +50,21 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class BluetoothOppServiceTest { @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock BluetoothMethodProxy mMethodProxy; private BluetoothOppService mService = null; private BluetoothAdapter mAdapter = null; @Mock private AdapterService mAdapterService; @Mock private AdapterService mAdapterService; @Before public void setUp() throws Exception { Assume.assumeTrue("Ignore test when BluetoothOppService is not enabled", BluetoothOppService.isEnabled()); MockitoAnnotations.initMocks(this); BluetoothMethodProxy.setInstanceForTesting(mMethodProxy); TestUtils.setAdapterService(mAdapterService); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); TestUtils.startService(mServiceRule, BluetoothOppService.class); Loading @@ -67,6 +77,7 @@ public class BluetoothOppServiceTest { @After public void tearDown() throws Exception { BluetoothMethodProxy.setInstanceForTesting(null); if (!BluetoothOppService.isEnabled()) { return; } Loading Loading @@ -119,4 +130,33 @@ public class BluetoothOppServiceTest { // 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()); } } Loading
android/app/src/com/android/bluetooth/opp/BluetoothOppService.java +11 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -184,7 +185,8 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti + BluetoothShare.USER_CONFIRMATION + "=" + BluetoothShare.USER_CONFIRMATION_PENDING; private static final String WHERE_INVISIBLE_UNCONFIRMED = @VisibleForTesting static final String WHERE_INVISIBLE_UNCONFIRMED = "(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND " + INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")"; Loading Loading @@ -1116,22 +1118,23 @@ public class BluetoothOppService extends ProfileService implements IObexConnecti } // 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 int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED, null); int delNum = BluetoothMethodProxy.getInstance().contentResolverDelete( contentResolver, BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED, null); if (V) { Log.v(TAG, "Deleted shares, number = " + delNum); } // Keep the latest inbound and successful shares. Cursor cursor = contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID}, Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery( contentResolver, BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID}, WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id if (cursor == null) { return; Loading @@ -1143,8 +1146,8 @@ 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); } Loading
android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java +46 −6 Original line number Diff line number Diff line Loading @@ -15,19 +15,26 @@ */ 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.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; import android.net.Uri; 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.BluetoothMethodProxy; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; Loading @@ -43,18 +50,21 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class BluetoothOppServiceTest { @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Mock BluetoothMethodProxy mMethodProxy; private BluetoothOppService mService = null; private BluetoothAdapter mAdapter = null; @Mock private AdapterService mAdapterService; @Mock private AdapterService mAdapterService; @Before public void setUp() throws Exception { Assume.assumeTrue("Ignore test when BluetoothOppService is not enabled", BluetoothOppService.isEnabled()); MockitoAnnotations.initMocks(this); BluetoothMethodProxy.setInstanceForTesting(mMethodProxy); TestUtils.setAdapterService(mAdapterService); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); TestUtils.startService(mServiceRule, BluetoothOppService.class); Loading @@ -67,6 +77,7 @@ public class BluetoothOppServiceTest { @After public void tearDown() throws Exception { BluetoothMethodProxy.setInstanceForTesting(null); if (!BluetoothOppService.isEnabled()) { return; } Loading Loading @@ -119,4 +130,33 @@ public class BluetoothOppServiceTest { // 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()); } }