Cursor

This app reads and displays the name and id number of each contact in the contacts table. Furthermore, the app updates itself automatically when you add a new contact.

Source code in Cursor.zip

  1. MainActivity.java. Class MainActivity implements the three methods of interface LoaderManager.LoaderCallbacks<Cursor>: onCreateLoader, onLoadFinished, onLoaderReset.
  2. activity_main.xml. I added the android:id attribute to the TextView, and removed the android:text attribute.
  3. strings.xml. I removed the string resource hello_world.
  4. AndroidManifest.xml. The <uses-permission> element has the name android.permission.READ_CONTACTS.
  5. build.gradle (Module: app)

Run the app

Before I ran this app, I used the People app to create contacts named Moe, Larry, Curly, Shemp, in that order. To keep it simple, I gave each contact no information other than its name. There are no addresses, phone numbers, etc.

Source code on GitHub

  1. ContactsContract.java. Class ContactsContract.Contacts contains static final fields. For example,
    1. ContactsContract.Contacts.CONTENT_URI contains the string "content://com.android.contacts/contacts" (line 1385).
    2. ContactsContract.Contacts._ID (inherited from class BaseColumns) contains the string "_id" (line 25 of BaseColumns.java)
    3. ContactsContract.Contacts.DISPLAY_NAME (inherited from class ContactsContract.ContactsColumns) contains the string "display_name" (line 809, from line 1056).

The projection array

The String array projection lists the names of fields we want the Cursor to read from each record in the database of contacts. In our app, these names are "_id" and "display_name". For the time being, this list does not have to include the _id field. But it will have to include the _id later, when we use this cursor in a CursorAdapter. See CursorAdapter.

Each field that we read has an identifying number called its index. In our app, the index of the _id field is 0, and the index of the display_name field is 1. The method getColumnIndex returns these index numbers.

A second thread

Using the UI thread to download the data from the table would take too long. The following machinery creates a second thread to do this download. See Loading Data in the Background and Loaders.

The initLoader method of the LoaderManager calls two methods of the LoaderManager.LoaderCallbacks<Cursor> object. This object is the MainActivity object, and the two methods are onCreateLoader and onLoadFinished, in that order. The first two parameters of initLoader are passed to onCreateLoader.

onCreateLoader creates a CursorLoader that knows which columns of which records (in what order) of which table we are interested in. (The full name of this class is android.content.CursorLoader; you may have to write its import statement by hand at the top of the MainActivity.java file.) This CursorLoader is returned to the LoaderManager, which runs the CursorLoader in a second thread. When the CursorLoader is finished, the LoadManager takes the Cursor produced by the CursorLoader and passes it to onLoadFinished. Although a Cursor has a close method, we should not call it ourselves. See onLoadFinished.

Good news: if the data in the table changes (e.g., if the user adds a new contact), the LoadManager will automatically run the CursorLoader again and pass a fresh Cursor to onLoadFinished. again. See Handling the Results: “When the data changes, the framework also re-runs the current query.” And Loaders: “[Loaders] monitor the source of their data and deliver new results when the content changes.”

There’s a third method of the LoaderManager.LoaderCallbacks<Cursor> that might be called by the LoadManager. onLoaderReset will be called if the Cursor passed to the most recent call to onLoadFinished has become obsolete (e.g., if the user created a new contact). In this case, we must make no further use of the Cursor.

No adapter

A Cursor is intended to be plugged into a CursorAdapter, which in turn is plugged into an AdapterView such as a Spinner or ListView. But to keep it simple, this app demonstrates the Cursor without the CursorAdapter and AdapterView. It does all the output with a plain vanilla TextView.

To operate the Cursor, this app manually calls four of its methods:

  1. moveToFirst
  2. getColumnIndex
  3. getString
  4. moveToNext
Our next app will plug the Cursor into a CursorAdapter, and the CursorAdapter will call these four methods.

Deprecated code

The old class Contacts is deprecated; we now use ContactsContract instead. The old methods managedQuery and startManagingCursor are deprecated; we now use a CursorLoader object instead. For an old-fashioned Cursor that uses class Contacts, launch ApiDemos and go to
Views → Lists → 02. Cursor (People)
which runs List2.java, which calls startManagingCursor.

Things to try

  1. The fourth argument of the CursorLoader constructor is currently null. Replace it with one of the following strings. Each string must be an expression that would be legal in an SQL WHERE clause.
    1. "_id >= 2"
    2. "_id = 2"

    3. "_id % 2 = 0"
      (only the even numbers)
    4. "_id % 2 = 1"
      (only the odd numbers)

    5. "length(display_name) = 5"
    6. "length(display_name) < 5"

    7. "display_name like '%y'"
      (the name ends with y)
    8. "not display_name like '%y'"
      (the name does not end with y)

    9. "_id <= 2 and display_name like '%e%'"
      (the name contains an e)
    10. "_id <= 2 and not display_name like '%e%'"
      (the name does not contain an e)
    11. "_id <= 2 or display_name like '%e%'"

  2. The contacts are stored in an SQLite database file named contacts2.db. To read and write data base files we use a command line shell named sqlite3. It runs on your Mac or PC, and it also runs on your Android emulator (AVD) or device.

    Open a Terminal window on Mac, or a cmd.exe window on PC. Using the Android Debug Bridge adb, which is in the ~/Library/Android/sdk/platform-tools directory (which is listed in your PATH environment variable), run a shell on the AVD. The shell prompt ends with #. Then run sqlite3 on the AVD. The sqlite3 prompt is sqlite>.

    adb version
    Android Debug Bridge version 1.0.32
    
    adb help
    
    adb devices
    List of devices attached
    192.168.56.101:5555	device
    
    adb -s 192.168.56.101:5555 shell
    
    root@vbox86p:/ # pwd
    /
    
    root@vbox86p:/ # cd /data/data/com.android.providers.contacts/databases
    
    root@vbox86p:/ # pwd
    /data/data/com.android.providers.contacts/databases
    
    root@vbox86p:/ # ls
    contacts2.db
    
    root@vbox86p:/ # ls -l
    -rw-rw---- u0_a2    u0_a2      331776 2015-07-14 09:07 contacts2.db
    
    root@vbox86p:/ # sqlite3 -version
    3.7.11 2012-03-20 11:35:50 00bb9c9ce4f465e6ac321ced2a9d0062dc364669
    
    root@vbox86p:/ # sqlite3 -help
    
    root@vbox86p:/ # sqlite3 contacts2.db
    SQLite version 3.7.11 2012-03-20 11:35:50
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    
    sqlite3> .help
    
    sqlite3> .tables
    raw_contacts (among others)
    
    sqlite3> .headers on
    sqlite3> .mode column
    sqlite3> .show
    
    sqlite3> select _id, display_name from raw_contacts;
    _id         display_name
    ----------  ------------
    1           Moe
    2           Larry
    3           Curly
    4           Shemp
    
    sqlite3> select _id, phonebook_label, display_name from raw_contacts;
    _id         phonebook_label display_name
    ----------  --------------- ------------
    1           M               Moe
    2           L               Larry
    3           C               Curly
    4           S               Shemp
    
    sqlite3> select * from raw_contacts;
    _id         account_id  sourceid    raw_contact_is_read_only  version     dirty       deleted     contact_id  aggregation_mode  aggregation_needed  custom_ringtone  send_to_voicemail  times_contacted  last_time_contacted  starred     pinned      display_name  display_name_alt  display_name_source  phonetic_name  phonetic_name_style  sort_key    phonebook_label  phonebook_bucket  sort_key_alt  phonebook_label_alt  phonebook_bucket_alt  name_verified  sync1       sync2       sync3       sync4
    ----------  ----------  ----------  ------------------------  ----------  ----------  ----------  ----------  ----------------  ------------------  ---------------  -----------------  ---------------  -------------------  ----------  ----------  ------------  ----------------  -------------------  -------------  -------------------  ----------  ---------------  ----------------  ------------  -------------------  --------------------  -------------  ----------  ----------  ----------  ----------
    1           1                       0                         2           1           0           1           0                 0                                    0                  0                                     0           2147483647  Moe           Moe               40                                  0                    Moe         M                13                Moe           M                    13                    0
    2           1                       0                         2           1           0           2           0                 0                                    0                  0                                     0           2147483647  Larry         Larry             40                                  0                    Larry       L                12                Larry         L                    12                    0
    3           1                       0                         2           1           0           3           0                 0                                    0                  0                                     0           2147483647  Curly         Curly             40                                  0                    Curly       C                3                 Curly         C                    3                     0
    4           1                       0                         2           1           0           4           0                 0                                    0                  0                                     0           2147483647  Shemp         Shemp             40                                  0                    Shemp       S                19                Shemp         S                    19                    0
    
    sqlite3> .quit
    root@vbox86p:/ # exit
    

  3. You can also copy the database from the AVD to your Mac or PC, and then run the sqlite3 on your Mac or PC. It’s in the directory
    ~/Library/Android/sdk/platform-tools. (You might have another copy of sqlite3 elsewhere on your Mac or PC. My Mac has one in /usr/bin.)

    cd
    adb -s 192.168.56.101:5555 pull /data/data/com.android.providers.contacts/databases/contacts2.db
    1201 KB/s (331776 bytes in 0.269s)
    
    ls -l contacts2.db     (on PC, dir contacts2.db)
    -rw-r--r--  1 myname  mygroup  331776 Jul 14 15:47 contacts2.db
    
    sqlite3 -version
    3.8.6 2014-08-15 11:46:33 9491ba7d738528f168657adb43a198238abde19e
    
    sqlite3 -help
    
    sqlite3 contacts2.db
    SQLite version 3.8.6 2014-08-15 11:46:33
    Enter ".help" for usage hints.
    
    sqlite> .tables
    raw_contacts (among others)
    
    sqlite> .headers on
    sqlite> .mode column
    sqlite> .show
    
    sqlite> select _id, display_name from raw_contacts;
    _id         display_name
    ----------  ------------
    1           Moe
    2           Larry
    3           Curly
    4           Shemp
    
    sqlite3> .quit
    

  4. Besides the contacts, what other SQLite databases are built into Android? How can we access them with an app and with sqlite3?
    adb -s 192.168.56.101:5555 shell
    root@vbox86p:/ # find / -type f -name '*.db'
    root@vbox86p:/ # find / -type f -name '*.db' | sort
    
    /data/data/com.android.deskclock/databases/alarms.db
    /data/data/com.android.documentsui/databases/recents.db
    /data/data/com.android.email/databases/EmailProvider.db
    /data/data/com.android.email/databases/EmailProviderBackup.db
    /data/data/com.android.email/databases/EmailProviderBody.db
    /data/data/com.android.keychain/databases/grants.db
    /data/data/com.android.launcher/cache/widgetpreviews.db
    /data/data/com.android.launcher/databases/launcher.db
    /data/data/com.android.providers.calendar/databases/calendar.db
    /data/data/com.android.providers.contacts/databases/contacts2.db
    /data/data/com.android.providers.contacts/databases/profile.db
    /data/data/com.android.providers.downloads/databases/downloads.db
    /data/data/com.android.providers.media/databases/external.db
    /data/data/com.android.providers.media/databases/internal.db
    /data/data/com.android.providers.settings/databases/settings.db
    /data/data/com.android.providers.telephony/databases/mmssms.db
    /data/data/com.android.providers.telephony/databases/telephony.db
    /data/data/com.android.providers.userdictionary/databases/user_dict.db
    /data/system/locksettings.db
    /data/system/users/0/accounts.db
    
    Launch the browser on your Android device or emulator if you have never done so before. Repeat the above find command. Is there a new .db file named /data/data/com.android.browser/databases/browser2.db?
    adb -e shell sqlite3 /data/data/com.android.launcher/databases/launcher.db 'select _id, title from favorites;'
    1|
    2|
    4|Gallery
    6|Settings
    8|Phone
    10|People
    12|Messaging
    14|Browser
    

    Let’s search for the find command itself. We’ll discover that find (and each of the other classic Unix programs) is a symbolic link to the executable file /system/xbin/busybox, the Swiss army knife of embedded Linux.

    root@vbox86p:/ # echo $PATH
    /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
    
    root@vbox86p:/ # echo $PATH | tr ':' '\n'
    /sbin
    /vendor/bin
    /system/sbin
    /system/bin
    /system/xbin
    
    root@vbox86p:/ # echo $PATH | tr ':' '\n' | cat -n
         1	/sbin
         2	/vendor/bin
         3	/system/sbin
         4	/system/bin
         5	/system/xbin
    
    root@vbox86p:/ # which find
    /system/xbin/find
    
    root@vbox86p:/ # cd /system/xbin
    root@vbox86p:/ # pwd
    /system/xbin/find
    
    root@vbox86p:/ # ls -l | more
    lrwxr-xr-x root     shell             2014-11-14 05:59 awk -> busybox
    lrwxr-xr-x root     shell             2014-11-14 05:59 find -> busybox
    lrwxr-xr-x root     shell             2014-11-14 05:59 grep -> busybox
    etc.
    
    root@vbox86p:/ # ls -l busybox
    -rwxr-xr-x root     shell     1218127 2014-11-14 05:54 busybox
    
    root@vbox86p:/ # exit
    

  5. Launch the browser on your Android device or emulator. Use sqlite3 to examine the tables bookmarks and history in the database /data/data/com.android.browser/databases/browser2.db. Then run an app that reads this database. Change the Uri to Browser.BOOKMARKS_URI. The projection array should hold the four strings
    1. Browser.BookmarkColumns._ID
    2. Browser.BookmarkColumns.BOOKMARK
    3. Browser.BookmarkColumns.TITLE
    4. Browser.BookmarkColumns.URL
    Change the fourth and sixth arguments of the CursorLoader constructor to null. Add the following element to AndroidManifest.xml.
        <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
    

    To display only the bookmarks, change the fourth argument of the CursorLoader constructor to

        Browser.BookmarkColumns.BOOKMARK + " = 1"