Подтвердить что ты не робот

Как читать данные MMS на Android?

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

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

4b9b3361

Ответ 1

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

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

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

Почему это важно? Ну, это стандартный способ получения 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 Содержание последнего SMS в этом разговоре. Если это 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 MMS
        } else {
            // it 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

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

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.

Ответ 2

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

На моем устройстве число курсоров равно 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;
}

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

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

Ответ 3

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

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

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

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

    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...
    
  • Когда вы используете ContentObserver для отслеживания содержимого сообщений для изменений, он вызывает несколько уведомлений для одного и того же сообщения. Я использую статическую переменную - в моем случае lastMMSID - для отслеживания сообщения.
  • Этот код хорошо работает для извлечения содержимого как входящих, так и исходящих сообщений. Важно перебирать все записи, возвращаемые "содержанием://mms/part/" uri, чтобы добраться до содержимого - текста и/или вложений - из MMS.
  • Единственный способ, которым я мог бы хорошо работать, чтобы отличать входящие и исходящие MMS, - проверить нулевой статус поля "m_id" содержимого mms-sms/conversations.

    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;
}

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

Ответ 4

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

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

Ответ 5

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

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

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

Эта таблица имеет совершенно другую схему из обычной Telephony.Threads.CONTENT_URI one (???); это проекция, которую использует приложение 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.

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