¿Cómo funcionan las columnas unidas implícitas con los datos de los contactos de Android?
Estoy consultando la tabla ContactsContract.Data
para encontrar registros de teléfono.
Tengo un error al crear un nuevo CursorLoader
:
- Obtener el perfil de usuario / propietario de contacto URI y la imagen de usuario con API 8 en adelante
- Obtener contactos de Android con funcionalidad de tipo a filtro, restringida a una cuenta específica
- ¿Cómo hago que mi Android ContentObserver para ContactsContract detecte un contacto añadido, actualizado o eliminado?
- Android API 8, 10 ContactsContract.Data.HAS_PHONE_NUMBER ninguna columna
- Recuperar número de teléfono de contacto de URI en Android
java.lang.IllegalArgumentException: Invalid column deleted
Mi código:
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Data; ... String[] projection = { Phone.DELETED, Phone.LOOKUP_KEY, Phone.NUMBER, Phone.TYPE, Phone.LABEL, Data.MIMETYPE, Data.DISPLAY_NAME_PRIMARY }; // "mimetype = ? AND deleted = ?" String selection = Data.MIMETYPE + " = ? AND " Phone.DELETED + " = ?"; String[] args = {Phone.CONTENT_ITEM_TYPE, "0"}; return new CursorLoader( this, Data.CONTENT_URI, projection, selection, args, null);
¿Alguna idea de por qué la columna Phone.DELETED
no está incluida en el cursor? La documentación dice –
Algunas columnas del contacto crudo asociado también están disponibles a través de una unión implícita .
- Cómo crear un contacto programáticamente
- Cómo actualizar sólo los contactos en lugar de agregar en el adaptador de sincronización
- Cómo buscar Contactos con dirección (FORMATTED_ADDRESS) con una consulta?
- ¿Cuál es el tipo / nombre de cuenta predeterminado para contactos en la aplicación de contacto de Android?
- Obtención de un cursor SINGLE con detalles de nombre completo y números de teléfono
- Leer todos los datos de contacto
- Android 3.0 - Cómo recuperar todos los contactos a través de ContactsContract
- Telephony.Sms.Inbox.PERSON utiliza Contacts.People._ID obsoletos
Parece que has encontrado una característica que se ha documentado en muchos lugares, pero aún no se había implementado. He abierto un error para el seguimiento de este problema – permite ver lo que los chicos de AOSP tienen que decir sobre el tema ( informe de errores ).
Mientras tanto, puede utilizar la solución siguiente:
Uri uri = ContactsContract.RawContactsEntity.CONTENT_URI; String[] projection = { Phone._ID, Phone.DELETED, //Phone.LOOKUP_KEY, Phone.NUMBER, Phone.TYPE, Phone.LABEL, Data.MIMETYPE, Data.DISPLAY_NAME_PRIMARY }; String selection = Data.MIMETYPE + " = ? AND " + Data.DELETED + " = ?"; String[] args = { Phone.CONTENT_ITEM_TYPE, "0" }; return new CursorLoader( this, uri, projection, selection, args, null);
Cambios:
- Usar el URI de RawContactsEntity
-
LOOKUP_KEY
no es accesible a través de URI arriba – tendrá que ejecutar consulta adicional si es absolutamente necesario esta columna -
_ID
será necesario si va a utilizar elCursor
resultante enCursorAdapter
.
Editar: después de la solicitud @ MichaelAlanHuff estoy publicando las partes de código que esta respuesta se basa en
De com.android.providers.contacts.ContactsProvider2#queryLocal()
(código fuente de ContactsProvider2 ):
protected Cursor queryLocal(final Uri uri, final String[] projection, String selection, String[] selectionArgs, String sortOrder, final long directoryId, final CancellationSignal cancellationSignal) { final SQLiteDatabase db = mDbHelper.get().getReadableDatabase(); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); String groupBy = null; String having = null; String limit = getLimit(uri); boolean snippetDeferred = false; // The expression used in bundleLetterCountExtras() to get count. String addressBookIndexerCountExpression = null; final int match = sUriMatcher.match(uri); switch (match) { ... case DATA: case PROFILE_DATA: { final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE); final int typeInt = getDataUsageFeedbackType(usageType, USAGE_TYPE_ALL); setTablesAndProjectionMapForData(qb, uri, projection, false, typeInt); if (uri.getBooleanQueryParameter(Data.VISIBLE_CONTACTS_ONLY, false)) { qb.appendWhere(" AND " + Data.CONTACT_ID + " in " + Tables.DEFAULT_DIRECTORY); } break; } ... } qb.setStrict(true); // Auto-rewrite SORT_KEY_{PRIMARY, ALTERNATIVE} sort orders. String localizedSortOrder = getLocalizedSortOrder(sortOrder); Cursor cursor = query(db, qb, projection, selection, selectionArgs, localizedSortOrder, groupBy, having, limit, cancellationSignal); if (readBooleanQueryParameter(uri, Contacts.EXTRA_ADDRESS_BOOK_INDEX, false)) { bundleFastScrollingIndexExtras(cursor, uri, db, qb, selection, selectionArgs, sortOrder, addressBookIndexerCountExpression, cancellationSignal); } if (snippetDeferred) { cursor = addDeferredSnippetingExtra(cursor); } return cursor; }
Como puede ver, hay dos métodos adicionales en los que se puede usar SQLiteQueryBuilder para crear la consulta: setTablesAndProjectionMapForData()
y el método query()
adicional.
Fuente de com.android.providers.contacts.ContactsProvider2#setTablesAndProjectionMapForData()
:
private void setTablesAndProjectionMapForData(SQLiteQueryBuilder qb, Uri uri, String[] projection, boolean distinct, boolean addSipLookupColumns, Integer usageType) { StringBuilder sb = new StringBuilder(); sb.append(Views.DATA); sb.append(" data"); appendContactPresenceJoin(sb, projection, RawContacts.CONTACT_ID); appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID); appendDataPresenceJoin(sb, projection, DataColumns.CONCRETE_ID); appendDataStatusUpdateJoin(sb, projection, DataColumns.CONCRETE_ID); appendDataUsageStatJoin( sb, usageType == null ? USAGE_TYPE_ALL : usageType, DataColumns.CONCRETE_ID); qb.setTables(sb.toString()); boolean useDistinct = distinct || !ContactsDatabaseHelper.isInProjection( projection, DISTINCT_DATA_PROHIBITING_COLUMNS); qb.setDistinct(useDistinct); final ProjectionMap projectionMap; if (addSipLookupColumns) { projectionMap = useDistinct ? sDistinctDataSipLookupProjectionMap : sDataSipLookupProjectionMap; } else { projectionMap = useDistinct ? sDistinctDataProjectionMap : sDataProjectionMap; } qb.setProjectionMap(projectionMap); appendAccountIdFromParameter(qb, uri); }
Aquí se ve la construcción del argumento de table
de la consulta final usando StringBuilder
que se está pasando a varios métodos append*()
. No voy a publicar su código fuente, pero realmente se join
las tablas que aparecen en los nombres de los métodos. Si la tabla rawContacts
se uniera, esperaría ver una llamada a algo como appendRawContactJoin()
aquí …
Para completar: el otro método query()
que he mencionado no modifica el argumento de la table
:
private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection, String selection, String[] selectionArgs, String sortOrder, String groupBy, String having, String limit, CancellationSignal cancellationSignal) { if (projection != null && projection.length == 1 && BaseColumns._COUNT.equals(projection[0])) { qb.setProjectionMap(sCountProjectionMap); } final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder, limit, cancellationSignal); if (c != null) { c.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI); } return c; }
La inspección de la cadena de métodos anterior me llevó a la conclusión de que existe una característica oficialmente documentada que no se aplica.
- Bibliotecas de Android en Android Studio
- Qué hacer cuando – java.io.FileNotFoundException: ¿No hay proveedor de contenido?