¿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 :

 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 .

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:

  1. Usar el URI de RawContactsEntity
  2. LOOKUP_KEY no es accesible a través de URI arriba – tendrá que ejecutar consulta adicional si es absolutamente necesario esta columna
  3. _ID será necesario si va a utilizar el Cursor resultante en CursorAdapter .

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.

  • ¿Cómo obtener ID de contacto, correo electrónico, número de teléfono en una consulta SQLite? Optimización para Android de los contactos
  • ¿Cómo obtener el ID de Facebook de un usuario a través de la API de ContactsContracts?
  • Actualización / Configuración de la imagen del perfil de usuario propio
  • Foto de contacto de Android: Cómo volver a (Google) foto de contacto de la web (y así, 'cancelar' un cambio de imagen local anterior)
  • ¿Cómo obtener contactos con los que el usuario habla a menudo?
  • Editar nombre / número de teléfono de contacto programáticamente
  • Insertar contacto en Android con ContactsContract
  • No se puede insertar los contactos de android mediante programación en el dispositivo de Android
  • Cómo mostrar personalizado tipo contacto dentro de Contacto como WhatsApp android
  • ¿Diferencia entre términos android?
  • Recibir cambios de contacto (agregados, eliminados y cambiados)
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.