Як читати MMS-дані в Android?


74

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

Я бачив схему mmssms.dbта їх колонку таблиці частин.


База mmssms.dbданих є частиною мікропрограми та не доступна програмам Android. Постачальник content://mms-sms/conversationsвмісту не є частиною SDK і не повинен бути доступним для програм Android.
CommonsWare

Я роблю щось подібне ТУТ! stackoverflow.com/questions/11556633 / ...
toobsco42

Відповіді:


279

Знайти документацію з цього приводу досить складно, тому я зберу тут всю інформацію, яку знайшов. Якщо ви поспішаєте або просто не любите читати, перейдіть до розділу Як отримати дані із SMS .

вміст: // mms-sms / бесіди

Це URI постачальника послуг MMS та SMS ..., що дозволяє нам запитувати бази даних MMS та SMS одночасно та змішувати їх в одному потоці (що називається розмовами ).

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

Отримайте список усіх бесід

Єдине, що вам потрібно зробити, це запитати content://mms-sms/conversationsUri:

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", залежно від того, чи є повідомлення, представлене рядком, 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

Це те саме, що отримання текстової частини ... єдина різниця полягає в тому, що ви будете шукати інший тип 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
Зміст вмісту: // mms-sms / бесіди. Ця URL-адреса містить список із усіма потоками. Але не окремі повідомлення (sms або mms). Тож немає сенсу знати sms чи mms, хоча це жодне з них.
Максим

чи є причина, чому всі мої типи MMS-mime повертаються як додаток / smil?
Джастін

2
Джастін, оскільки MMS зберігаються в базі даних як слайд-шоу за допомогою SMIL.
Наба

3
content://mms-sms/conversationsпрацює не на всіх телефонах (у мене є Galaxy S6, і він не працює). Мені довелося використовувати content://mms/все це.
KVISH

1
Якщо я не помиляюсь, MessageFormat.format("content://mms/{0}/addr", id);це працюватиме лише для ідентифікаторів, які мають менше 1000. Чи не повинно бути MessageFormat.format("content://mms/{0,number,#}/addr", id);?
Нік

9

Відповідь Крістіана чудова. Однак спосіб отримання адреси відправника для мене не спрацював. Оператор 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;
}

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

Я припускаю, це спрацювало на нього. Схоже, це дало б правильну відповідь, якби виняток відбувся у першому рядку.


1
Я б не намагався викликати parseLong у поле contact_id; розглядати це як струну. Насправді, цілком можливо, що це може бути адреса електронної пошти чи щось інше, у випадку шлюзів електронної пошти до mms.
Едвард Фальк

4
Щоб уточнити typeконстанти, вони походять із PduHeadersкласу: 0x97/ 151 є PduHeaders.TOта 0x89/ 137 є PduHeaders.FROM. Іншими дійсними значеннями для посилання є: 0x81/ 129 є PduHeaders.BCCта 0x82/ 130 є PduHeaders.CC. Див. Також Telephony.Mms.Addr .
zelanix

5

Я просто боровся з цим; однак, нарешті, я змусив це заробити, і я думав, що ця тема може отримати користь з мого досвіду.

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

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

Ця таблиця має зовсім іншу схему, ніж звичайна Telephony.Threads.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 у варіанті "просто = істинно" не є надійним, натомість мені довелося використовувати дату з останнього повідомлення Sms або Mms.

Ще одне, що я мабуть мав би згадати, це те, що для того, щоб отримати належний список повідомлень для певного потоку, мені довелося зробити запит як у постачальників MMS, так і у SMS, потім об’єднати результати в один список, а потім відсортувати їх за датою.

Я перевірив поведінку на Android 5.x та 7.x.

Я сподіваюся, це допоможе трохи більше.


3

Мені довелося внести деякі зміни, щоб змусити це працювати для мене.

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

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

Усім хоробрим воїнам, котрі йшли переді мною на цій посаді, - я щиро дякую тобі!


1

Відповідь, наведена вище для отримання getMMSAddress (), не повинна містити цикл while (cursor.moveToNext ()) ;. Він повинен витягувати адресу лише з першого елемента курсору. З якоїсь невідомої мені причини цей курсор має більше одного запису. Перший містить адресу відправника. Інші елементи курсору за першим містять адресу одержувача. Таким чином, код, як є, повертає адресу одержувача, а не адресу відправника.

Це було дуже корисно для розкриття вмісту MMS.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.