Как читать данные MMS в Android? - PullRequest
71 голосов
/ 10 июня 2010

Я хочу прочитать данные MMS. Я видел таблицу деталей в mmssms.db, где хранятся записи ммс; Я использую курсор, и я хочу знать соответствующий URI; Я использую "content: // mms-sms / разговоры" и имена столбцов из "Адрес" (отправлено), "Текст" или "Тема" и "столбец" имени изображения.

Я видел схему mmssms.db и столбец их таблицы деталей.

Ответы [ 5 ]

267 голосов
/ 23 июня 2011

Трудно найти документацию по этому поводу, поэтому я соберу здесь всю информацию, которую нашел. Если вы спешите или просто не любите читать, перейдите к разделу Как получить данные из раздела SMS .

содержание: // MMS-SMS / разговоры

Это URI поставщика Mms и SMS ..., который позволяет нам одновременно запрашивать базы данных MMS и SMS и смешивать их в одном потоке (которые называются * 1009). * разговоры ).

Почему важен URI? Ну, это стандартный способ получения сообщений MMS и SMS; например, когда вы получите SMS и нажмете на панель уведомлений, он отправит сообщение о трансляции, например: content://mms-sms/conversations/XXX, где XXX - идентификатор разговора.

Получить список всех разговоров

Единственное, что вам нужно сделать, это запросить content://mms-sms/conversations Uri:

ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"*"};
Uri uri = Uri.parse("content://mms-sms/conversations/");
Cursor query = contentResolver.query(uri, projection, null, null, null);

Примечание: обычно, когда вы вызываете query и хотите вернуть все столбцы, вы можете передать null в качестве параметра projection. Однако вы не можете сделать это с этим провайдером, поэтому я использую *.

Теперь вы можете перебирать Cursor как обычно. Вот наиболее важные столбцы, которые вы хотели бы использовать:

  • _id - идентификатор сообщения. Капитан очевиден для спасения? Не совсем. Этот идентификатор можно использовать для получения подробной информации, используя content://sms или content://mms.
  • date объяснений не требуется.
  • thread_id - идентификатор разговора
  • body Содержание последнего СМС в этом разговоре. Если это MMS, даже если оно имеет текстовую часть, это будет null.

Примечание: , если вы запросите content://mms-sms/conversations, он вернет список различных разговоров, для которых _id является последним SMS или MMS в каждом разговоре. Если вы запросите content://mms-sms/conversations/xxx, он вернет каждое SMS и / или MMS в разговоре с идентификатором xxx.

Как различить SMS и MMS

Обычно вы хотите знать, какой тип сообщения вы обрабатываете. Документация гласит:

Виртуальный столбец, MmsSms.TYPE_DISCRIMINATOR_COLUMN, май быть запрошенным в прогнозе для запрос. Его значение либо "ммс", либо "смс", в зависимости от того, сообщение, представленное строкой, является MMS-сообщение или SMS-сообщение, соответственно.

Я думаю, что это относится к этой переменной ... однако я не смог заставить ее работать. Если у вас есть, пожалуйста, сообщите мне, как или отредактировать этот пост.

Пока что это то, что я сделал, и, похоже, это работает, но должны быть лучшие способы:

ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"_id", "ct_t"};
Uri uri = Uri.parse("content://mms-sms/conversations/");
Cursor query = contentResolver.query(uri, projection, null, null, null);
if (query.moveToFirst()) {
    do {
        String string = query.getString(query.getColumnIndex("ct_t"));
        if ("application/vnd.wap.multipart.related".equals(string)) {
            // it's MMS
        } else {
            // it's SMS
        }
    } while (query.moveToNext());
}

Как получить данные из SMS

Итак, у вас есть идентификатор SMS, тогда единственное, что вам нужно сделать, это:

String selection = "_id = "+id;
Uri uri = Uri.parse("content://sms");
Cursor cursor = contentResolver.query(uri, null, selection, null, null);
String phone = cursor.getString(cursor.getColumnIndex("address"));
int type = cursor.getInt(cursor.getColumnIndex("type"));// 2 = sent, etc.
String date = cursor.getString(cursor.getColumnIndex("date"));
String body = cursor.getString(cursor.getColumnIndex("body"));

Как получить данные из данных MMS?

MMS немного отличаются. Они могут быть построены с различными частями (текст, аудио, изображения и т. Д.); поэтому здесь будет показано, как получить каждый тип данных отдельно.

Итак, давайте предположим, что у нас есть идентификатор MMS в переменной mmsId. Мы можем получить подробную информацию об этом MMS с помощью поставщика content://mms/:

Uri uri = Uri.parse("content://mms/");
String selection = "_id = " + mmsId;
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);

Однако, единственный интересный столбец - это read, который равен 1, если сообщение уже прочитано.

Как получить текстовый контент из MMS

Здесь мы должны использовать content://mms/part ... например:

String selectionPart = "mid=" + mmsId;
Uri uri = Uri.parse("content://mms/part");
Cursor cursor = getContentResolver().query(uri, null,
    selectionPart, null, null);
if (cursor.moveToFirst()) {
    do {
        String partId = cursor.getString(cursor.getColumnIndex("_id"));
        String type = cursor.getString(cursor.getColumnIndex("ct"));
        if ("text/plain".equals(type)) {
            String data = cursor.getString(cursor.getColumnIndex("_data"));
            String body;
            if (data != null) {
                // implementation of this method below
                body = getMmsText(partId);
            } else {
                body = cursor.getString(cursor.getColumnIndex("text"));
            }
        }
    } while (cursor.moveToNext());
}

Он может содержать разные части текста ... но обычно это будет только один. Так что если вы хотите удалить цикл, он будет работать большую часть времени. Вот как выглядит метод getMmsText:

private String getMmsText(String id) {
    Uri partURI = Uri.parse("content://mms/part/" + id);
    InputStream is = null;
    StringBuilder sb = new StringBuilder();
    try {
        is = getContentResolver().openInputStream(partURI);
        if (is != null) {
            InputStreamReader isr = new InputStreamReader(is, "UTF-8");
            BufferedReader reader = new BufferedReader(isr);
            String temp = reader.readLine();
            while (temp != null) {
                sb.append(temp);
                temp = reader.readLine();
            }
        }
    } catch (IOException e) {}
    finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {}
        }
    }
    return sb.toString();
}

Как получить изображение из MMS

Это то же самое, что получить текстовую часть ... единственное отличие в том, что вы будете искать другой тип пантомимы:

String selectionPart = "mid=" + mmsId;
Uri uri = Uri.parse("content://mms/part");
Cursor cPart = getContentResolver().query(uri, null,
    selectionPart, null, null);
if (cPart.moveToFirst()) {
    do {
        String partId = cPart.getString(cPart.getColumnIndex("_id"));
        String type = cPart.getString(cPart.getColumnIndex("ct"));
        if ("image/jpeg".equals(type) || "image/bmp".equals(type) ||
                "image/gif".equals(type) || "image/jpg".equals(type) ||
                "image/png".equals(type)) {
            Bitmap bitmap = getMmsImage(partId);
        }
    } while (cPart.moveToNext());
}

Вот как выглядит метод getMmsImage:

private Bitmap getMmsImage(String _id) {
    Uri partURI = Uri.parse("content://mms/part/" + _id);
    InputStream is = null;
    Bitmap bitmap = null;
    try {
        is = getContentResolver().openInputStream(partURI);
        bitmap = BitmapFactory.decodeStream(is);
    } catch (IOException e) {}
    finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {}
        }
    }
    return bitmap;
}

Как получить адрес отправителя

Вам нужно будет использовать провайдера content://mms/xxx/addr, где xxx - это идентификатор MMS:

private String getAddressNumber(int id) {
    String selectionAdd = new String("msg_id=" + id);
    String uriStr = MessageFormat.format("content://mms/{0}/addr", id);
    Uri uriAddress = Uri.parse(uriStr);
    Cursor cAdd = getContentResolver().query(uriAddress, null,
        selectionAdd, null, null);
    String name = null;
    if (cAdd.moveToFirst()) {
        do {
            String number = cAdd.getString(cAdd.getColumnIndex("address"));
            if (number != null) {
                try {
                    Long.parseLong(number.replace("-", ""));
                    name = number;
                } catch (NumberFormatException nfe) {
                    if (name == null) {
                        name = number;
                    }
                }
            }
        } while (cAdd.moveToNext());
    }
    if (cAdd != null) {
        cAdd.close();
    }
    return name;
}

Заключительные мысли

  • Не могу понять, почему Google с этими тысячами миллионов долларов не платит ни студенту, ни кому-либо еще за документирование этого API. Вы должны проверить исходный код, чтобы узнать, как он работает, и, что еще хуже, они не публикуют те константы, которые используются в столбцах базы данных, поэтому мы должны написать их вручную.
  • Для других видов данных внутри MMS вы можете применить ту же идею, что и выше ... это просто вопрос знания типа mime.
7 голосов
/ 12 октября 2012

Ответ Кристиана превосходен. Однако метод получения адреса отправителя у меня не сработал. Оператор Long.parseLong ничего не делает, за исключением того, что, возможно, выдает исключение и новую строку (...)?.

На моем устройстве количество курсоров составляет 2 или более. Первый тип обычно имеет «тип» 137, а другие имеют «тип» 151. Я не могу найти, где это задокументировано, но можно сделать вывод, что 137 «от», а 151 «к». Таким образом, если я запускаю метод как есть, я не получаю исключения, и он возвращает последнюю строку, которая является получателем и во многих случаях только одной из нескольких.

Также AFAICT выбор не требуется, поскольку все строки имеют одинаковый msg_id. Тем не менее, это не больно.

Вот что мне нужно, чтобы получить адрес отправителя:

public static String getMMSAddress(Context context, String id) {
    String addrSelection = "type=137 AND msg_id=" + id;
    String uriStr = MessageFormat.format("content://mms/{0}/addr", id);
    Uri uriAddress = Uri.parse(uriStr);
    String[] columns = { "address" };
    Cursor cursor = context.getContentResolver().query(uriAddress, columns,
            addrSelection, null, null);
    String address = "";
    String val;
    if (cursor.moveToFirst()) {
        do {
            val = cursor.getString(cursor.getColumnIndex("address"));
            if (val != null) {
                address = val;
                // Use the first one found if more than one
                break;
            }
        } while (cursor.moveToNext());
    }
    if (cursor != null) {
        cursor.close();
    }
    // return address.replaceAll("[^0-9]", "");
    return address;
}

Мне было все равно, все ли это числа, но я включил способ исключить все, кроме цифр, в качестве комментария, если это необходимо. Он также может быть легко изменен, чтобы вернуть всех получателей.

Полагаю, это сработало для него. Похоже, он дал бы правильный ответ, если бы исключение произошло в первой строке.

4 голосов
/ 28 февраля 2017

Я только что боролся с этим;тем не менее, я наконец-то заставил его работать и подумал, что этот поток может извлечь пользу из моего опыта.

Я мог бы запросить на content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI) и получить адреса и детали, как описано в цепочке, но я обнаружил, что этот URIне будет извлекать потоки, которые только содержат MMS-сообщения - например, потоки с более чем двумя корреспондентами.

После некоторой копки в источнике приложения AOSP MMS, я обнаружил, что это былоиспользуя вариант на Telephony.Threads.CONTENT_URI для генерации списка разговоров - он добавлял параметр «простой» со значением «истина».добавив этот параметр, я обнаружил, что провайдер будет запрашивать совершенно другую таблицу, в которой действительно есть все потоки SMS и MMS.

Эта таблица имеет схему, совершенно отличную от обычной телефонии..CONTENT_URI один (???);это проекция, которую использует приложение AOSP -

public static final String[] ALL_THREADS_PROJECTION = {
    Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS,
    Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR,
    Threads.HAS_ATTACHMENT
};

Здесь _ID - это идентификатор потока, то есть идентификатор для Telephony.Sms.CONTENT_URI или Telephony.Mms.CONTENT_URI.

После того, как я обнаружил эту странную деталь, все стало работать намного лучше!Однако обратите внимание, что столбец DATE в варианте "simple = true" ненадежен, вместо этого мне пришлось использовать дату из самого последнего сообщения Sms или Mms.

Еще одна вещь, которую я, вероятно, должен упомянуть, это то, что дляЧтобы получить правильный список сообщений для определенной цепочки, мне пришлось запросить у обоих провайдеров Mms и Sms, затем объединить результаты в один список и отсортировать их по дате.

Я проверил поведение на Android 5.x и 7.x.

Надеюсь, это поможет немного больше.

3 голосов
/ 18 апреля 2013

Мне пришлось внести некоторые изменения, чтобы заставить это работать на меня.

  1. Когда я получаю cursor.getString (cursor.getColumnIndex ("type")) из содержимого mms-sms / разговоров, ("content: // mms-sms / разговор / / ") Я проверяю значение поля" type "на null. Если переменная равна нулю - т.е.

    String otype = c.getString(c.getColumnIndex("type"));
    if(otype != null) {
        //this is an sms - handle it...
    

    сообщение - это SMS, иначе это MMS. Для MMS вы должны проверить оба типа пантомимы следующим образом: -

    if (("application/vnd.wap.multipart.related".equalsIgnoreCase(msg_type)
        ||"application/vnd.wap.multipart.mixed".equalsIgnoreCase(msg_type))
        && !id.equalsIgnoreCase(lastMMSID)) {
             //this is a MMS - handle it...
    
  2. Когда вы используете ContentObserver для мониторинга содержимого сообщения на предмет изменений, он запускает несколько уведомлений для одного и того же сообщения. Я использую статическую переменную - в моем случае lastMMSID - для отслеживания сообщения.
  3. Этот код хорошо работает для извлечения содержимого как входящих, так и исходящих сообщений. Важно перебрать все записи, возвращаемые uri "content: // mms / part /", чтобы получить доступ к контенту - тексту и / или вложениям - MMS.
  4. Единственный способ, с помощью которого я довольно хорошо различаю входящие и исходящие MMS, - это проверить нулевой статус поля "m_id" содержимого mms-sms / разговоров.

    String m_id = c.getString(c.getColumnIndex("m_id"));
    String mDirection = m_id == null? "OUT": "IN";
    

Последняя мысль о том, как получить поле адреса. По какой-то причине адресное содержимое не любит запрашивать параметр {"*"}, но это работает: -

final String[] projection = new String[] {"address", "contact_id", "charset", "type"};

Если это исходящее сообщение, «тип» для поиска будет 151. Для входящего сообщения «тип» будет 137. Полнофункциональный фрагмент кода будет выглядеть примерно так: -

private String getANumber(int id) {
    String add = "";
    final String[] projection = new String[] {"address","contact_id","charset","type"};
    final String selection = "type=137 or type=151"; // PduHeaders
    Uri.Builder builder = Uri.parse("content://mms").buildUpon();
    builder.appendPath(String.valueOf(id)).appendPath("addr");

    Cursor cursor = context.getContentResolver().query(
        builder.build(),
        projection,
        selection,
        null, null);

if (cursor.moveToFirst()) {
          do {
              String add = cursor.getString(cursor.getColumnIndex("address"));
              String type: cursor.getString(cursor.getColumnIndex("type"));
          } while(cursor.moveToNext());
      }
      // Outbound messages address type=137 and the value will be 'insert-address-token'
      // Outbound messages address type=151 and the value will be the address
      // Additional checking can be done here to return the correct address.
      return add;
}

Всем храбрым воинам, которые шли до меня на этом посту - я благодарю вас от всего сердца!

2 голосов
/ 13 февраля 2013

Ответ, приведенный выше для получения getMMSAddress (), не должен содержать цикл while (cursor.moveToNext ()) ;. Следует только извлечь адрес из первого элемента в курсоре. По какой-то причине, которая мне неизвестна, этот курсор имеет более одной записи. Первый содержит адрес отправителя. Другие элементы курсора, кроме первого, содержат адрес получателя. Таким образом, код возвращает адрес получателя, а не адрес отправителя.

Это было очень полезно для взлома содержимого MMS.

...