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

Commit 56ae3e46 authored by Sal Savage's avatar Sal Savage
Browse files

Null check and print error messages for failed MMS/SMS DB interactions

MAP Client requires the Telephony MMS/SMS DB to be up to store messages.
It should always be up when the feature is declared, but we still want
to avoid crashes and have useful error messages when it is not.

A longer term refactor to prevent messaging until the DB is up (or
whatever we decide the right thing is) should come in the future so we
can be even more robust to this.

Bug: 339911228
Flag: EXEMPT, null check + logging changes only, with tests
Test: atest com.android.bluetooth.mapclient.MapClientContentTest
Change-Id: I72490da32ae4429548170a7a04e75ddc3e18dfd1
parent 68fcce00
Loading
Loading
Loading
Loading
+25 −6
Original line number Diff line number Diff line
@@ -269,6 +269,11 @@ class MapClientContent {
        values.put(Sms.SEEN, seen);

        Uri results = mResolver.insert(contentUri, values);
        if (results == null) {
            error("Failed to get SMS URI, insert failed. Dropping message.");
            return;
        }

        mHandleToUriMap.put(handle, results);
        mUriToHandleMap.put(results, new MessageStatus(handle, readStatus));
        debug("Map InsertedThread" + results);
@@ -375,6 +380,11 @@ class MapClientContent {
            values.put(Mms.MESSAGE_SIZE, mmsBmessage.getSize());

            Uri results = mResolver.insert(contentUri, values);
            if (results == null) {
                error("Failed to get MMS entry URI. Cannot store MMS parts. Dropping message.");
                return;
            }

            mHandleToUriMap.put(handle, results);
            mUriToHandleMap.put(results, new MessageStatus(handle, read));

@@ -385,9 +395,6 @@ class MapClientContent {
            }

            storeAddressPart(message, results);

            values.put(Mms.Part.CONTENT_TYPE, "plain/text");
            values.put(Mms.SUBSCRIPTION_ID, mSubscriptionId);
        } catch (Exception e) {
            error("Error while storing MMS: " + e.toString());
            throw e;
@@ -406,6 +413,12 @@ class MapClientContent {

        Uri contentUri = Uri.parse(messageUri.toString() + "/part");
        Uri results = mResolver.insert(contentUri, values);

        if (results == null) {
            warn("failed to insert MMS part");
            return null;
        }

        debug("Inserted" + results);
        return results;
    }
@@ -415,17 +428,23 @@ class MapClientContent {
        Uri contentUri = Uri.parse(messageUri.toString() + "/addr");
        String originator = getOriginatorNumber(message);
        values.put(Mms.Addr.CHARSET, DEFAULT_CHARSET);

        values.put(Mms.Addr.ADDRESS, originator);
        values.put(Mms.Addr.TYPE, ORIGINATOR_ADDRESS_TYPE);
        mResolver.insert(contentUri, values);

        Uri results = mResolver.insert(contentUri, values);
        if (results == null) {
            warn("failed to insert originator address");
        }

        Set<String> messageContacts = new ArraySet<>();
        getRecipientsFromMessage(message, messageContacts);
        for (String recipient : messageContacts) {
            values.put(Mms.Addr.ADDRESS, recipient);
            values.put(Mms.Addr.TYPE, RECIPIENT_ADDRESS_TYPE);
            mResolver.insert(contentUri, values);
            results = mResolver.insert(contentUri, values);
            if (results == null) {
                warn("failed to insert recipient address");
            }
        }
    }

+83 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
@@ -465,6 +466,50 @@ public class MapClientContentTest {
        MapClientContent.clearAllContent(mMockContext);
    }

    /** Test that we gracefully exit when there's a problem with the SMS/MMS DB being available */
    @Test
    public void testInsertSmsFails_messageHandleNotInteractable() {
        // Try to store an MMS, but make the content resolver fail to insert and provide a null URI
        MissingContentProvider missingContentProvider =
                Mockito.spy(new MissingContentProvider(mMockContext));
        mMockContentResolver.addProvider("sms", missingContentProvider);
        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
        mMapClientContent.storeMessage(
                mTestMessage1, mTestMessage1Handle, mTestMessage1Timestamp, MESSAGE_SEEN);

        // Because the insert failed, function calls to update or delete this message should not
        // work either
        mMapClientContent.markRead(mTestMessage1Handle);
        verify(missingContentProvider, never())
                .update(any(Uri.class), any(ContentValues.class), any(Bundle.class));

        mMapClientContent.deleteMessage(mTestMessage1Handle);
        verify(missingContentProvider, never())
                .delete(any(Uri.class), anyString(), any(String[].class));
    }

    /** Test that we gracefully exit when there's a problem with the SMS/MMS DB being available */
    @Test
    public void testInsertMmsPartsSkippedWhenMmsInsertFails_messageHandleNotInteractable() {
        // Try to store an MMS, but make the content resolver fail to insert and provide a null URI
        MissingContentProvider missingContentProvider =
                Mockito.spy(new MissingContentProvider(mMockContext));
        mMockContentResolver.addProvider("mms", missingContentProvider);
        mMapClientContent = new MapClientContent(mMockContext, mCallbacks, mTestDevice);
        mMapClientContent.storeMessage(
                mTestMessage2, mTestMessage2Handle, mTestMessage1Timestamp, MESSAGE_SEEN);

        // Because the insert failed, function calls to update or delete this message should not
        // work either
        mMapClientContent.markRead(mTestMessage2Handle);
        verify(missingContentProvider, never())
                .update(any(Uri.class), any(ContentValues.class), any(Bundle.class));

        mMapClientContent.deleteMessage(mTestMessage2Handle);
        verify(missingContentProvider, never())
                .delete(any(Uri.class), anyString(), any(String[].class));
    }

    /**
     * Test verifying dumpsys does not cause Bluetooth to crash (esp since we're querying the
     * database to generate dump).
@@ -550,4 +595,42 @@ public class MapClientContentTest {
            return 0;
        }
    }

    public class MissingContentProvider extends FakeContentProvider {
        MissingContentProvider(Context context) {
            super(context);
        }

        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            // nothing deleted
            return 0;
        }

        @Override
        public Uri insert(Uri uri, ContentValues values) {
            // Insert fails, so there's no URI that points to the inserted values
            return null;
        }

        @Override
        public Cursor query(
                Uri uri,
                String[] projection,
                String selection,
                String[] selectionArgs,
                String sortOrder) {
            // Return empty cursor
            Cursor cursor = Mockito.mock(Cursor.class);
            when(cursor.moveToFirst()).thenReturn(false);
            when(cursor.moveToNext()).thenReturn(false);
            return cursor;
        }

        @Override
        public int update(Uri uri, ContentValues values, Bundle extras) {
            // zero rows updated
            return 0;
        }
    }
}