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.
MainActivity.java
.
Class
MainActivity
implements the three methods of interface
LoaderManager.LoaderCallbacks<Cursor>
:
onCreateLoader
,
onLoadFinished
,
onLoaderReset
.
activity_main.xml
.
I added the
android:id
attribute to the
TextView
,
and removed the
android:text
attribute.
strings.xml
.
I removed the
string
resource
hello_world
.
AndroidManifest.xml
.
The
<uses-permission>
element has the
name
android.permission.READ_CONTACTS
build.gradle
(Module: 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.
ContactsContract.java
.
Class
ContactsContract.Contacts
contains static final fields.
For example,
ContactsContract.Contacts.CONTENT_URI
contains the string
"content://com.android.contacts/contacts"
(line 1385).
ContactsContract.Contacts._ID
(inherited from class
BaseColumns
)
contains the string
"_id"
(line 25
of
BaseColumns.java
)
ContactsContract.Contacts.DISPLAY_NAME
(inherited from class
ContactsContract.ContactsColumns
)
contains the string
"display_name"
(line 809,
from
line 1056).
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.
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
.
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:
Cursor
into a
CursorAdapter
,
and the
CursorAdapter
will call these four methods.
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
.
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.
"_id >= 2"
"_id = 2"
"_id % 2 = 0"
"_id % 2 = 1"
"length(display_name)
= 5"
"length(display_name)
< 5"
"display_name
like '%y'"
y
)
"not display_name
like '%y'"
y
)
"_id <= 2 and display_name
like '%e%'"
e
)
"_id <= 2 and not display_name
like '%e%'"
e
)
"_id <= 2 or display_name like '%e%'"
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
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
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.dbLaunch 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
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
Browser.BookmarkColumns._ID
Browser.BookmarkColumns.BOOKMARK
Browser.BookmarkColumns.TITLE
Browser.BookmarkColumns.URL
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"