Search code examples
androiddatabasesmsandroid-contentresolverandroid-8.0-oreo

Oreo (8.0) - Remove sms programmatically


I can't remove the particular message on Android 8.0

    private void foo() {
        Uri dummySms = insertDummySms(context, threadId);
        removeDummySms(context, dummySms);
    }

    private Uri insertDummySms(Context context, long threadId) {
        ContentValues values = new ContentValues();
        values.put("thread_id", threadId);
        values.put("body", "Dummy SMS body.");
        Uri insert = context.getContentResolver().insert(Uri.parse("content://sms/sent"), values);
        Log.i(TAG, "insertDummySms: " + insert);
        return insert;
    }

    private void removeDummySms(Context context, Uri uri) {
        Log.i(TAG, "removeDummySms: START: " + uri.toString() + " :: " + context.getContentResolver().getType(uri));
        context.getContentResolver().delete(uri, null, null);
        Log.i(TAG, "removeDummySms: END!!!");
    }

When I run foo() method the logs shows:

I/Test: insertDummySms: content://sms/sent//8
I/Test: removeDummySms: START: content://sms/sent//8 :: vnd.android.cursor.item/sms,

then it crashes:

java.lang.IllegalArgumentException: Unknown URL
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:165)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)

The same code works on android 6.x and 7.x

The funny thing: If I remove every sms like this:

context.getContentResolver().delete(uri, null, null); // where uri is equal: content://sms

it works like a charm.

Any ideas why it fails?

Currently I have only one phone with Android O, so I don't know if it crashes on every Android O device. I have Nokia TA-1004


Solution

  • A relatively recent change to the SmsProvider class kinda broke the expected functionality there. The Uri returned from the Provider's insert() call was previously figured very simply as:

    Uri uri = Uri.parse("content://" + table + "/" + rowID);
    

    where rowID is the return from an insert() call on an SQLiteDatabase.

    For the insertion of a regular, complete SMS message, as in your case, table is "sms". However, there are several other tables, mostly used by the system and default messaging app, for which the same insert() method is used; e.g., "raw", "attachments", etc. Inserts on these tables resulted in invalid authorities on the returned Uris; e.g. content://raw/123.

    Newer versions now throw Exceptions on invalid authorities, rather than failing silently, so the Uri construction was altered to:

    Uri uri;
    if (table == TABLE_SMS) {
        uri = Uri.withAppendedPath(url, "/" + rowID);
    } else {
        uri = Uri.withAppendedPath(url, "/" + table + "/" + rowID );
    }
    

    and later to:

    Uri uri = Uri.withAppendedPath(url, String.valueOf(rowID));
    

    to address issues in the first.

    In both revisions, url is the one you passed in the ContentResolver#insert() call. For the insert() call in your snippet, this ends up returning a URI similar to content://sms/sent/123, wherein lies the issue. When that is passed in a delete() call, it doesn't match any URIs that the Provider's corresponding method deems valid, so it throws an IllegalArgumentException of Unknown URL.

    To fix this, you could construct your own valid URI from the return, as the ID is really all you need to know. For example:

    Uri del = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, dummySms.getLastPathSegment());
    

    However, it is arguably incorrect to do an insert on "content://sms/sent" (Sms.Sent.CONTENT_URI) to begin with. That URI, and the other similar "box" URIs - i.e., Sms.Inbox.CONTENT_URI, Sms.Drafts.CONTENT_URI, etc. - are meant to be used with queries, rather than write operations.

    You should instead insert on the base Sms.CONTENT_URI ("content://sms"), and set the TYPE to MESSAGE_TYPE_SENT in the ContentValues. For example, the recommended fix:

    ContentValues cv = new ContentValues();
    cv.put(Telephony.Sms.ADDRESS, ...);
    cv.put(Telephony.Sms.BODY, ...);
    cv.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_SENT);
    
    Uri uri = getContentResolver().insert(Telephony.Sms.CONTENT_URI, cv);
    

    This will result in a Uri that SmsProvider's delete() method will recognize, and your delete should succeed as before.