20

Pytam o tabelę ContactsContract.Data, aby znaleźć rekordy telefonu.W jaki sposób niejawne połączone kolumny współpracują z danymi kontaktów Androida?

otrzymuję komunikat o błędzie, gdy tworzę nową CursorLoader:

java.lang.IllegalArgumentException: Invalid column deleted 

mój kodu:

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

jakiś pomysł, dlaczego kolumna Phone.DELETED nie jest zawarty w kursorem? documentation mówi -

Niektóre kolumny z powiązanego surowego kontaktu dostępne są również przez niejawny przyłączyć.

+0

Czy dotyczy to wielu urządzeń? –

+0

@MichaelAlanHuff - tak, próbowałem na dwóch urządzeniach. Android 5.0 i 5.1. – Gautam

Odpowiedz

5

Wygląda na to, że znalazłeś funkcję, która została udokumentowana w wielu miejscach, ale jeszcze nie została zaimplementowana. Otworzyłem błąd do śledzenia tego problemu - zobaczmy, co faceci AOSP mają do powiedzenia na ten temat (bug report).

Tymczasem, można użyć następującego rozwiązania:

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

Zmiany:

  1. Używaj RawContactsEntity's URI
  2. LOOKUP_KEY nie jest dostępny za pośrednictwem powyżej URI - będziesz musiał wykonać dodatkowe zapytania jeśli koniecznie potrzebujesz tej kolumny, będzie wymagana kolumna, jeśli zamierzasz użyć e wynikiem Cursor w CursorAdapter.

Edycja: po @ życzenie MichaelAlanHuff za jestem delegowania części kodu, która to odpowiedź jest oparta na

Od com.android.providers.contacts.ContactsProvider2#queryLocal() (kod źródłowy 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; 


} 

jak ty można zauważyć, że istnieją dwie dodatkowe metody, w których SQLiteQueryBuilder użyte do zbudowania zapytania mogą zostać zmienione: setTablesAndProjectionMapForData() i dodatkowa metoda query().

Źródło 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); 
} 

Tu zobaczysz budowę table argumentu ostatecznego zapytania używając StringBuilder który jest przekazywany do kilku append*() metod. Nie zamierzam publikować kodu źródłowego, ale tak naprawdę są to tabele pojawiające się w nazwach metod. Jeśli dołączysz do tabeli rawContacts, spodziewałbym się, że zobaczę tutaj coś podobnego do appendRawContactJoin() ...

Dla kompletności: drugi query() metoda wspominałem nie zmienia table argument:

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

Kontrola powyższego łańcucha metod doprowadziło mnie do wniosku, że nie jest oficjalnie udokumentowane funkcja, która nie jest wdrożony.

+0

Dzięki za odpowiedź. Nie spodziewałem się, że coś zostanie udokumentowane, a nie zaimplementowane. Zwykle jest odwrotnie. :) – Gautam

+0

Btw, czy masz pojęcie, co może być nie tak tutaj: http://stackoverflow.com/q/30783516/886468 – Gautam

+2

@ Gautam, zgadzam się, że jest to bardzo nietypowe. Zrobiłem kilka kopii kodu źródłowego, ponieważ nie mogłem w to uwierzyć. Miej oko na błąd, który otworzyłem - może nadal mam coś w niewłaściwy sposób. – Vasiliy