The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Content Provider Theory

Android publishes data to you via an abstraction known as a “content provider”. Access to contacts and the call log, for example, are given to you via a set of content providers. In a few places, Android expects you to supply a content provider, such as for integrating your own search suggestions with the Android Quick Search Box. And, content providers are one way for you to supply data to third party applications, or to consume information from third party applications. As such, content providers have the potential to be something you would encounter frequently, even if in practice they do not seem used much.

Prerequisites

Understanding this chapter requires that you have read the core chapters, particularly the one on working with local databases.

Using a Content Provider

Any Uri in Android that begins with the content:// scheme represents a resource served up by a content provider. Content providers offer data encapsulation using Uri instances as handles – you neither know nor care where the data represented by the Uri comes from, so long as it is available to you when needed. The data could be stored in a SQLite database, or in flat files, or retrieved off a device, or be stored on some far-off server accessed over the Internet.

Given a Uri, you may be able to perform basic CRUD (create, read, update, delete) operations using a content provider. Uri instances can represent either collections or individual pieces of content. Given a collection Uri, you may be able to create new pieces of content via insert operations. Given an instance Uri, you may be able to read data represented by the Uri, update that data, or delete the instance outright. Or, given a Uri, you may be able to open up a handle to what amounts to a file, that you can read and, possibly, write to.

These are all phrased as “may” because the content provider system is a facade. The actual implementation of a content provider dictates what you can and cannot do, and not all content providers will support all capabilities.

Pieces of a Uri

A Uri for a ContentProvider is made up of two to four components.

A provider Uri always has a content scheme. So, when represented as a string, you will see the Uri start with content://.

After the scheme, where in an http:// URL you would find a domain name or IP address, a provider Uri always has the authority string. This is unique on the device — only one provider will be tied to a given authority string.

What comes after the authority string is up to the provider. It is structured like the path segments of an http:// URL, but what those path segments mean is up to the provider implementation. The one approximate rule is that a Uri pointing to an individual piece of content — such as a row of a table or view in a database – frequently has the Uri end in a number, where the number indicates a unique identifier of that content.

Most of the Android APIs expect these to be Uri objects, though in common discussion, it is simpler to think of them as strings. The Uri.parse() static method creates a Uri out of the string representation.

Getting a Handle

So, where do these Uri instances come from?

Some Uri values are part of the framework. For example, ContactsContract.Contacts.CONTENT_URI is a Uri pointing at the collection of contacts.

You might also get Uri instances handed to you from other sources, such as getting Uri handles for contacts via activities responding to ACTION_PICK or ACTION_GET_CONTENT Intent objects.

You can also hard-wire literal String objects (e.g., "content://contacts/people") and convert them into Uri instances via Uri.parse(). This is not an ideal solution, as the base Uri values could conceivably change over time. For example, while you used to access contacts via a Uri like content://contacts/people, that is no longer the case. ContactsContract.Contacts.CONTENT_URI is a different value and will give you better results.

The Database-Style API

Of the two flavors of API that a content provider may support, the database-style API is more prevalent. Using a ContentResolver, you can perform standard “CRUD” operations (create, read, update, delete) using what looks like a SQL interface.

Makin’ Queries

Given a base Uri, you can run a query to return data out of the content provider related to that Uri. This has much of the feel of SQL: you specify the “columns” to return, the constraints to determine which “rows” to return, a sort order, etc. The difference is that this request is being made of a content provider, not directly of some database (e.g., SQLite).

You have two main options for running a query:

  1. Use the query() method on ContentResolver from some sort of background thread
  2. Use a CursorLoader, as is discussed in an upcoming chapter

The standard query() method on ContentResolver takes five parameters:

This method returns a Cursor object, which you can use to retrieve the data returned by the query.

This will hopefully make more sense given an example. This chapter shows some sample bits of code from the ContentProvider/ConstantsPlus sample project. This is the same basic application as was first shown back in the chapter on database access, but rewritten to pull the database logic into a content provider, which is then used by a retained ListFragment.

As before, in onViewCreated(), we kick off a LoadCursorTask if we do not already have our Cursor, such as via a configuration change:

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    SimpleCursorAdapter adapter=
        new SimpleCursorAdapter(getActivity(), R.layout.row,
            current, new String[] {
            DatabaseHelper.TITLE,
            DatabaseHelper.VALUE },
            new int[] { R.id.title, R.id.value },
            0);

    setListAdapter(adapter);

    if (current==null) {
      task=new LoadCursorTask(getActivity()).execute();
    }
  }

LoadCursorTask inherits from a BaseTask. BaseTask and its subclasses need a ContentResolver to be able to work with our ContentProvider. So, BaseTask takes a Context in its constructor and uses that to retrieve a ContentResolver:

  abstract private class BaseTask<T> extends AsyncTask<T, Void, Cursor> {
    final ContentResolver resolver;

    BaseTask(Context ctxt) {
      super();

      resolver=ctxt.getContentResolver();
    }

In doInBackground(), LoadCursorTask calls a doQuery() method inherited from BaseTask, which in turn uses our ContentResolver to query our ContentProvider:

    protected Cursor doQuery() {
      Cursor result=resolver.query(Provider.Constants.CONTENT_URI,
          PROJECTION, null, null, null);

      result.getCount();

      return(result);
    }

In the call to query(), we provide:

  1. The Uri for our provider (Provider.Constants.CONTENT_URI), in this case representing the collection of physical constants managed by the provider
  2. A list of properties to retrieve
  3. Three null values, indicating that we do not need a constraint clause (the Uri represents the instance we need), nor parameters for the constraint, nor a sort order (we should only get one entry back)

The biggest “magic” here is the list of properties. The lineup of what properties are possible for a given provider should be provided by the documentation (or source code) for the content provider itself. In this case, we define logical values on the Provider provider implementation class that represent the various properties (namely, the unique identifier, the display name or title, and the value of the constant), and we refer to them with our PROJECTION:

  private static final String[] PROJECTION=new String[] {
      Provider.Constants._ID, Provider.Constants.TITLE,
      Provider.Constants.VALUE };

Adapting to the Circumstances

Now that we have a Cursor via query(), we have access to the query results and can do whatever we want with them. You might, for example, manually extract data from the Cursor to populate widgets or other objects.

In our case, we are using the SimpleCursorAdapter, set up in onViewCreated(), to render our Cursor. This means that we need to take the Cursor that doQuery() generates and arrange to hand that to the SimpleCursorAdapter. The onPostExecute() method on BaseTask handles this:

    @Override
    public void onPostExecute(Cursor result) {
      ((CursorAdapter)getListAdapter()).changeCursor(result);
      current=result;
      task=null;
    }

Give and Take

Of course, content providers would be astonishingly weak if you couldn’t add or remove data from them, and were instead limited to only update what is there. Fortunately, content providers offer these abilities as well.

To insert data into a content provider, you have two options available on the ContentProvider interface (available through getContentResolver() to your activity):

The insert() method returns a Uri for you to use for future operations on that new object. The bulkInsert() method returns the number of created rows; you would need to do a query to get back at the data you just inserted.

For example, if the user chooses our “Add” overflow item, we pop up a dialog to collect a new constant:

  private void add() {
    LayoutInflater inflater=getActivity().getLayoutInflater();
    View addView=inflater.inflate(R.layout.add_edit, null);
    AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());

    builder.setTitle(R.string.add_title).setView(addView)
        .setPositiveButton(R.string.ok, this)
        .setNegativeButton(R.string.cancel, null).show();
  }

Then, if the user taps the “OK” button in the dialog, our onClick() listener is called, where we collect the entered values from the user, pour them into a ContentValues structure, and pass that to an InsertTask:

  @Override
  public void onClick(DialogInterface dialog, int which) {
    ContentValues values=new ContentValues(2);
    AlertDialog dlg=(AlertDialog)dialog;
    EditText title=(EditText)dlg.findViewById(R.id.title);
    EditText value=(EditText)dlg.findViewById(R.id.value);

    values.put(DatabaseHelper.TITLE, title.getText().toString());
    values.put(DatabaseHelper.VALUE, value.getText().toString());

    task=new InsertTask(getActivity()).execute(values);
  }

InsertTask, in its doInBackground() method, calls insert() on a ContentResolver to insert this row:

    @Override
    protected Cursor doInBackground(ContentValues... values) {
      resolver.insert(Provider.Constants.CONTENT_URI, values[0]);

      return(doQuery());
    }

Notice that we also call doQuery() again. That is because our Cursor is now out of date, and we need to obtain a fresh Cursor with fresh results. And, as with LoadTask, InsertTask inherits from BaseTask, not only providing us with that doQuery() method but also the onPostExecute() method that puts the Cursor into the SimpleCursorAdapter.

To delete one or more rows from the content provider, use the delete() method on ContentResolver. This works akin to a SQL DELETE statement and takes three parameters:

The Streaming API

Sometimes, what you are trying to retrieve does not look like a set of rows and columns, but rather looks like a stream. For example, the MediaStore provider manages the index of all music, video, and image files available on external storage, and you can use MediaStore to open up a stream to read in the contents of one of those files. Here, working with the Uri and the provider is much like working with a URL and a Web server.

Some content providers, like MediaStore, support both the database-style and streaming APIs — you query to find media that matches your criteria, then can open some file that matches. Other content providers might only support the streaming API.

Working with the Stream

Given a Uri that represents some file managed by the content provider, you can use openInputStream() and openOutputStream() on a ContentResolver to access an InputStream or OutputStream, respectively. Note, though, that not all content providers may support both modes. For example, a content provider that serves files stored inside the application (e.g., assets in the APK file), you will not be able to get an OutputStream to modify the content.

Also note that openInputStream() and openOutputStream() work with both file:// and content:// Uri values — you do not need to manually inspect the Uri and handle files separately if you do not want to.

Retrieving Metadata

You can call getType() on a ContentResolver, supplying a Uri as a parameter. This will return the MIME type reported by the ContentProvider for the data at that Uri. For the streaming API, this will give you results reminiscent of a Web server — some specific MIME type if the provider knows it, otherwise probably some generic MIME type (e.g., application/octet-stream).

You can also call query() on the ContentResolver. Your projection (the list of columns to return) can include:

The DATA Anti-Pattern

However, the authors of MediaStore screwed up developer expectations, due to a legacy convention.

The legacy convention was that a content:// Uri might not be openable directly using something like openInputStream(). Instead, it pointed to a database row, retrievable via query(), and you would look in the DATA column for how to access the actual data. Some providers no doubt continue to use this pattern, as does MediaStore. The rules for what the DATA column would be were not well documented, but by convention they tended to be a path to a file. The problem is that this runs afoul of Google’s current guidance, as there is no guarantee that other apps can access such a file.

Do not blindly assume that if you get a content:// Uri that it is for the DATA pattern. Try to open a stream on the Uri, and if that fails, then see if the DATA pattern is in play. Or, if you query() to get the size and/or display name first, also request the DATA column, and if it exists and is not null, try that if opening the stream directly does not work.

Building Content Providers

The preview of this section was traded for a bag of magic beans.

Issues with Content Providers

The preview of this section was the victim of a MITM ('Martian in the middle') attack.