Получите конкретную контактную информацию из URI, возвращенной с Intent.ACTION_PICK

Я пишу приложение для Android, которое имеет тип данных, который представляет человека (в частности, родителя или опекуна ребенка). Я хотел бы иметь возможность «импортировать» соответствующие поля данных из базы данных «Контакты» на устройстве Android. (Это должно быть необязательным, то есть не будет требованием, чтобы родитель/опекун уже находился в базе данных «Контакты», а также не обновлялась база данных «Контакты», если они добавляют новых родителей/опекунов.)

До сих пор я написал код для начала нового намерения, чтобы выбрать конкретный контакт (используя Intent.ACTION_PICK). Затем я получаю URI, который представляет конкретный контакт в базе данных.

К сожалению, я не знаю, что следующий шаг. Похоже, это должно быть самым простым в мире, но, видимо, нет. Я прочитал документацию на веб-сайте разработчика Android, и я просмотрел несколько книг Android. Нет радости.

Конкретную информацию, которую я хотел бы получить, это:

  1. Имя контакта (по возможности, первый и последний отдельно)

  2. Адрес электронной почты контакта (основной)

  3. Номер мобильного телефона контакта

Я предполагаю, что это должно быть возможно, запросив использование ContentResolver, но я не знаю, как это сделать с URI, возвращаемым из Intent. В большинстве документов предполагается, что у вас есть Contact ID, а не URI контакта. Кроме того, я понятия не имею, какие поля я могу помещать в проекцию для запроса, полагая, что это даже правильный способ делать то, что я хочу.

Вот мой стартовый код:

// In a button's onClick event handler:
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT);

// In onActivityResult:
if (resultCode == RESULT_OK) {
    if (requestCode == PICK_CONTACT) {
        contactURI = data.getData();
       //NOW WHAT?
    }
}
15
nl ja de

3 ответы

Хорошо, после многих копаний я нашел то, что, как я считаю, был ответом. Решения, которые я нашел, отличаются в зависимости от того, какой уровень API Android вы используете. Тем не менее, они совсем не такие, поэтому, если есть лучшие решения, я бы с удовольствием узнал.

В любом случае первым шагом является получение идентификатора контакта, выполнив запрос в URI, возвращенный из Intent.ACTION_PICK. Пока мы здесь, мы также должны получить отображаемое имя, а строка, представляющая, имеет ли контакт номер телефона или нет. (Мы не будем нуждаться в них для современного решения, но мы будем нуждаться в них для устаревшего решения.)

String id, name, phone, hasPhone;
int idx;
Cursor cursor = getContentResolver().query(contactUri, null, null, null, null);
if (cursor.moveToFirst()) {
    idx = cursor.getColumnIndex(ContactsContract.Contacts._ID);
    id = cursor.getString(idx);

    idx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
    name = cursor.getString(idx);

    idx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
    hasPhone = cursor.getString(idx);
}

Для записи столбцы, возвращенные из этого URI, являются большей частью полями, представленными константами в ContactsContract.Profile (включая константы, унаследованные от других интерфейсов). Не включены PHOTO_FILE_ID, PHOTO_THUMBNAIL_URI или PHOTO_URI (но PHOTO_ID есть ).

Теперь, когда у нас есть идентификатор, нам нужно получить соответствующие данные. Первое (и самое простое) решение - запросить Entity. Запросы Entity запрашивают сразу все данные контактов для контакта или необработанного контакта. Каждая строка представляет собой один Raw Contact, доступ к которому осуществляется с помощью констант в ContactsContract. Contacts.Entity . Обычно вы будете иметь дело только с RAW_CONTACT_ID, DATA1 и MIMETYPE. Однако, если вы хотите, чтобы имя и фамилия были указаны отдельно, тип имени MIME содержит имя в DATA2 и фамилию в DATA3.

Вы загружаете переменные, сопоставляя столбец MIMETYPE с ContactsContract.CommonDataKinds константы; например, тип MIME для электронной почты находится в ContactsContract.CommonDataKinds. Email.CONTENT_ITEM_TYPE .

// Build the Entity URI.
Uri.Builder b = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id).buildUpon();
b.appendPath(ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
URI contactUri = b.build();

// Create the projection (SQL fields) and sort order.
String[] projection = {
        ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
        ContactsContract.Contacts.Entity.DATA1,
        ContactsContract.Contacts.Entity.MIMETYPE };
String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC";
cursor = getContentResolver().query(contactUri, projection, null, null, sortOrder);

String mime;
int mimeIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.MIMETYPE);
int dataIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.DATA1);
if (cursor.moveToFirst()) {
    do {
        mime = cursor.getString(mimeIdx);
        if (mime.equalsIgnoreCase(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
            email = cursor.getString(dataIdx);
        }
        if (mime.equalsIgnoreCase(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
            phone = cursor.getString(dataIdx);
        }
       //...etc.
    } while (cursor.moveToNext());
}

К сожалению, Entities не были внедрены в API 11 (Android 3.0, Honeycomb), что означает, что этот код несовместим с примерно 65% устройств Android на рынке (на момент написания этой статьи). Если вы попробуете, вы получите IllegalArgumentException из URI.

Второе решение - построить строку запроса и сделать один запрос для каждого типа данных, который вы хотите использовать:

// Get phone number - if they have one
if ("1".equalsIgnoreCase(hasPhone)) {
    cursor = getContentResolver().query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null,
            ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = "+ id, 
            null, null);
    if (cursor.moveToFirst()) {
        colIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
        phone = cursor.getString(colIdx);
    }
    cursor.close();
}

// Get email address
cursor = getContentResolver().query(
        ContactsContract.CommonDataKinds.Email.CONTENT_URI,
        null,
        ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + id,
        null, null);
if (cursor.moveToFirst()) {
    colIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS);
    email = cursor.getString(colIdx);
}
cursor.close();

// ...etc.

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

Решение, которое я придумал, - попробовать версию, которая использует запросы Entity, поймать IllegalArgumentException и поместить унаследованный код внутри блока catch:

try {
   //Build the Entity URI.
    Uri.Builder b = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id).buildUpon();
    b.appendPath(ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
       //...etc...
} catch (IllegalArgumentException e) {
   //Get phone number - if they have one
    if ("1".equalsIgnoreCase(hasPhone)) {   
       //...etc...
} finally {
   //If you want to display the info in GUI objects, put the code here
}

Я надеюсь, что это помогает кому-то. И, опять же, если есть лучшие способы сделать это, я все уши.

17
добавлено
спасибо, на основе этого ответа я невосстановил силу таблицы «сущность»
добавлено автор DoruChidean, источник
как получить тип телефона, будь то домашний или мобильный?
добавлено автор Han Whiteking, источник

Как оказалось, там есть лучший способ сделать это.

Как я уже упоминал, класс ContactsContract.Contacts.Entity не был доступен до API 11. Однако ContactsContract . Класс был доступен обратно в API 5, и вы можете использовать этот класс в основном так же, как вы используете класс Entity.

Я обновил свой код. Он очень похож на код класса Entity и работает в основном таким же образом. Тем не менее, я протестировал его на своем телефоне под управлением Gingerbread, и все работает отлично.

Одно изменение, которое я должен был сделать, следующее: не существует способа получить ContactsContract.Data.RAW_CONTACT_ID из исходного запроса, а этот идентификатор - not так же, как идентификатор, который вы получаете, например <Код> ContactsContract.Contacts._ID . Вместо этого я запросил константу ContactsContract.Contacts.DISPLAY_NAME , которая согласована почти в каждом классе ContactsContract.

Вот рабочий код:

        Cursor cursor; //Cursor object
        String mime;   //MIME type
        int dataIdx;   //Index of DATA1 column
        int mimeIdx;   //Index of MIMETYPE column
        int nameIdx;   //Index of DISPLAY_NAME column

       //Get the name
        cursor = getContentResolver().query(params[0],
                new String[] { ContactsContract.Contacts.DISPLAY_NAME },
                null, null, null);
        if (cursor.moveToFirst()) {
            nameIdx = cursor.getColumnIndex(
                    ContactsContract.Contacts.DISPLAY_NAME);
            name = cursor.getString(nameIdx);

           //Set up the projection
            String[] projection = {
                    ContactsContract.Data.DISPLAY_NAME,
                    ContactsContract.Contacts.Data.DATA1,
                    ContactsContract.Contacts.Data.MIMETYPE };

           //Query ContactsContract.Data
            cursor = getContentResolver().query(
                    ContactsContract.Data.CONTENT_URI, projection,
                    ContactsContract.Data.DISPLAY_NAME + " = ?",
                    new String[] { name },
                    null);

            if (cursor.moveToFirst()) {
               //Get the indexes of the MIME type and data
                mimeIdx = cursor.getColumnIndex(
                        ContactsContract.Contacts.Data.MIMETYPE);
                dataIdx = cursor.getColumnIndex(
                        ContactsContract.Contacts.Data.DATA1);

               //Match the data to the MIME type, store in variables
                do {
                    mime = cursor.getString(mimeIdx);
                    if (ContactsContract.CommonDataKinds.Email
                            .CONTENT_ITEM_TYPE.equalsIgnoreCase(mime)) {
                        email = cursor.getString(dataIdx);
                    }
                    if (ContactsContract.CommonDataKinds.Phone
                            .CONTENT_ITEM_TYPE.equalsIgnoreCase(mime)) {
                        phone = cursor.getString(dataIdx);
                        phone = PhoneNumberUtils.formatNumber(phone);
                    }
                } while (cursor.moveToNext());
            }
        }
15
добавлено
Это был первый метод, который на самом деле работал из многих, которые я нашел, некоторые комментарии, хотя, 1. Это довольно дорогие запросы и 2. Вы хотите передать значение data.getData() в ваша задача async, где data - это Intent, переданный onActivityResult
добавлено автор barnacle.m, источник
@GerardGrundy Karl реализует это решение в AsyncTask, которое рекомендуется из-за потенциально длительных операций запроса на распознаватель контента. Массив params [] (можно назвать то, что вы хотите, но «params» и «args» являются общими именами) заключается в том, как данные передаются в AsyncTask. Для получения дополнительной информации просмотрите документацию AsyncTask .
добавлено автор Slerig, источник
Я получаю предупреждение о параметрах [0], что это?
добавлено автор uplearnedu.com, источник
Большое спасибо за ваши ответы!
добавлено автор Chisko, источник
Могу ли я узнать, как получить тип телефона?
добавлено автор Han Whiteking, источник
//Add a permission to read contacts data to your application manifest.


//Use Intent.ACTION_PICK in your Activity 
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK,
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI);
        startActivityForResult(contactPickerIntent, RESULT_PICK_CONTACT);

//Then Override the onActivityResult() and retrieve the ID,Phone number and Name in the data. 
 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       //check whether the result is ok
        if (resultCode == RESULT_OK) {
           //Check for the request code, we might be usign multiple startActivityForReslut
            switch (requestCode) {
            case RESULT_PICK_CONTACT:
               Cursor cursor = null;
        try {
            String phoneNo = null ;
            String name = null;           
            Uri uri = data.getData();            
            cursor = getContentResolver().query(uri, null, null, null, null);
            cursor.moveToFirst();           
            int  phoneIndex =cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            phoneNo = cursor.getString(phoneIndex); 
            textView2.setText(phoneNo);
        } catch (Exception e) {
            e.printStackTrace();
        }
                break;
            }
        } else {
            Log.e("MainActivity", "Failed to pick contact");
        }
    }

Примечание. Перед Android 2.3 (уровень API 9) выполнение запроса в поставщике контактов (например, показанного выше) требует, чтобы ваше приложение объявляло разрешение READ_CONTACTS (см. Раздел «Безопасность и разрешения»). Однако, начиная с Android 2.3, приложение «Контакты/Люди» предоставляет вашему приложению временное разрешение на чтение из поставщика контактов, когда оно возвращает результат. Временное разрешение применяется только к запрошенному конкретному контакту, поэтому вы не можете запрашивать контакт, отличный от того, который указан Uri намерения, если вы не объявите разрешение READ_CONTACTS.

1
добавлено
Mobile Dev Jobs — вакансии и аналитика
Mobile Dev Jobs — вакансии и аналитика
6 187 участник(ов)

Публикуем вакансии и запросы на поиск работы по направлению iOS, Android, Xamarin и т.д. ВАЖНО: Правила публикации и правила канала: Ссылка – https://telegra.ph/Pravila-oformleniya-vakansij-i-rezyume-11-09-2

Android Developers
Android Developers
4 476 участник(ов)

Общаемся на темы, посвященным Android-разработке, SDK, Kotlin, Realm и т.д.

Android Architecture
Android Architecture
2 186 участник(ов)

Русскоязычный чат по архитектуре в андроид приложениях. Подробнее: http://telegra.ph/Android-Architecture-12-24

rus-speaking/android
rus-speaking/android
1 705 участник(ов)

Основной чат по Android разработке (вопрос-ответ). ПРАВИЛА: bit.ly/andr-rules. NEWS: bit.ly/AnrdResId ЧАТЫ: Основной: bit.ly/andr-main IDE, сборка, Git, сервисы: bit.ly/andr-tools Оффтоп: bit.ly/andr-offtop Конференции, события: bit.ly/andr-events Вакансии, найм: bit.ly/andr-job Архитектура: bit.ly/andr-patterns Rx: bit.ly/andr-rx Тестирование: bit.ly/andr-test Kotlin: bit.ly/andr-kotlin Хаmarin: bit.ly/andr-xamarin За мат, спам, агрессию, предложения о работе, оффтоп в этом канале - бан на сутки и более ☢☢☢

Android Dev Подкаст
Android Dev Подкаст
1 325 участник(ов)

Комната для обсуждения Android Dev подкаста apptractor.ru/AndroidDev/ Общее обсуждение Android: https://t.me/android_ru Остальные чаты про Android: http://t.me/devChats Наши новости https://t.me/androiddevpodcast_news

Android Guards
Android Guards
602 участник(ов)

Обсуждение любых вопросов касающихся безопасности Android. - Защита системы и приложений - Уязвимости и эксплойты - Вредоносное ПО - Копание в кишках системы и приложений (RE)

Android JOB
Android JOB
466 участник(ов)

Публикуем вакансии и запросы на поиск работы по направлению Android (full-time, part-time, remote и разовые подработки)

AndroidDev :: Разработка. It's Android time now!
AndroidDev :: Разработка. It's Android time now!
458 участник(ов)

It's Android time now! Чат разработчиков Android. Вакансии, резюме и информацию о митапах размещать можно. Публикацию скрытой и явной рекламы ваших каналов и сайтов после получения разрешения от @olegushakov

Aandroid Talks!
Aandroid Talks!
212 участник(ов)

Чат об общих вопросах по ОС Android. Чат для разработки под андроид - pro.android: https://t.me/joinchat/AAAAAEKIFKnmRT9cMebb9w

Android Rus
Android Rus
68 участник(ов)