Android Basic Training Course: Building ContentProvider

    In my previous post, I had used standard Content Provider. With it, you can load data from device contacts, media, sd card files,...Moreover, it can be customized by developers to access data from Internet, SQLite database or Files. Whether in the circumstances, it also stands as an intermediary transporter like this description diagram:
    In this post, I will present the way to customizing ContentProvider to loading data from a SQLite database, this can reduce data loading time. In order to prepare well for the understanding of this issue, you can read the previous post about use SQLite database in Android.
    DEMO VIDEO:


Creating a database class

    In Android, SQLite database created by extending SQLiteOpenHelper. So, make a simple subclass and overriding onCreate() and onUpgrade() like this:
DBHelper.java
package info.devexchanges.contentproviderwithsqlitedb;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBHelper extends SQLiteOpenHelper {
    private static final int DB_VERSION = 1;
    private static final String DB_NAME = "db_friend";

    public static final String TABLE_FRIENDS = "friend";
    public static final String ID = "id";
    public static final String COL_NAME = "name";
    public static final String COL_JOB = "job";

    private static final String CREATE_TABLE_FRIENDS = "create table " + TABLE_FRIENDS
            + " (" + ID + " integer primary key autoincrement, " + COL_NAME
            + " text not null, " + COL_JOB + " text not null);";

    public DBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_FRIENDS);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_FRIENDS);
        onCreate(db);
    }
}

Building Content Provider

    By making a subclass of ContentProvider, we have to override these require methods:
  • onCreate():which is called to initialize the provider. Only called from the application main thread, and must avoid performing lengthy operations, other methods below didn't.
  • query(): which returns data to the caller.
  • insert(): which inserts new data into the content provider.
  • delete(): which deletes data from the content provider.
  • update(): which updates existing data in the content provider.
  • getType(): which returns the MIME type of data in the content provider.
     By creating this content provider class, you must create a new database instance in it, so onCreate() method will be like:

    @Override
    public boolean onCreate() {
        dbHelper = new DBHelper(getContext());

        // permissions to be writable
        database = dbHelper.getWritableDatabase();
        if (database == null)
            return false;
        else
            return true;
    }

    Content providers work with data at the URI level. For instance, this URI identifies all of the records:
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + FRIENDS_BASE_PATH);

    As you can see, we must provide an authority for any custom content provider as the name of the content provider. In this example, it is:
private static final String AUTHORITY = "info.devexchanges.contentprovider.CustomContentProvider";

    In query() method, for getting data from database, we must call getReadableDatabase() method of SQLiteOpenHelper before start query by using Cursor. On the contrary, deleting, updating, inserting need getWritableDatabase(). These changing data methods use ContentResolver to access and alter data. Full code for this content provider, after overriding these important methods:
CustomContentProvider.java
package info.devexchanges.contentproviderwithsqlitedb;

import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;

public class CustomContentProvider extends ContentProvider {

    private DBHelper dbHelper;
    private SQLiteDatabase database;
    private static final String AUTHORITY = "info.devexchanges.contentprovider.CustomContentProvider";
    public static final int FRIENDS = 100;
    public static final int FRIEND_ID = 110;

    private static final String FRIENDS_BASE_PATH = "friend";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + FRIENDS_BASE_PATH);

    public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/mt-tutorial";
    public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/mt-tutorial";
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        uriMatcher.addURI(AUTHORITY, FRIENDS_BASE_PATH, FRIENDS);
        uriMatcher.addURI(AUTHORITY, FRIENDS_BASE_PATH + "/#", FRIEND_ID);
    }

    @Override
    public boolean onCreate() {
        dbHelper = new DBHelper(getContext());

        // permissions to be writable
        database = dbHelper.getWritableDatabase();
        if (database == null)
            return false;
        else
            return true;
    }

    @SuppressWarnings("ConstantConditions")
    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setTables(DBHelper.TABLE_FRIENDS);

        int uriType = uriMatcher.match(uri);
        switch (uriType) {
            case FRIEND_ID:
                queryBuilder.appendWhere(DBHelper.ID + "=" + uri.getLastPathSegment());
                break;
            case FRIENDS:
                break;
            default:
                throw new IllegalArgumentException("Unknown URI");
        }

        Cursor cursor = queryBuilder.query(dbHelper.getReadableDatabase(),
                projection, selection, selectionArgs, null, null, sortOrder);
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        return null;
    }

    @SuppressWarnings("ConstantConditions")
    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long row = database.insert(DBHelper.TABLE_FRIENDS, "", values);

        // If record is added successfully
        if (row > 0) {
            Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
            getContext().getContentResolver().notifyChange(newUri, null);
            return newUri;
        }
        throw new SQLException("Fail to add a new record into " + uri);

    }

    @SuppressWarnings("ConstantConditions")
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int uriType = uriMatcher.match(uri);
        int rowsAffected = 0;
        switch (uriType) {
            case FRIENDS:
                rowsAffected = database.delete(DBHelper.TABLE_FRIENDS, selection, selectionArgs);
                break;
            case FRIEND_ID:
                String id = uri.getLastPathSegment();
                if (TextUtils.isEmpty(selection)) {
                    rowsAffected = database.delete(DBHelper.TABLE_FRIENDS, DBHelper.ID + "=" + id, null);
                } else {
                    rowsAffected = database.delete(DBHelper.TABLE_FRIENDS, selection + " and " + DBHelper.ID + "=" + id, selectionArgs);
                }
                break;
            default:
                throw new IllegalArgumentException("Unknown or Invalid URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);

        return rowsAffected;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

Registering in AndroidManifest

    All custom content provider class must be register in AndroidManifest.xml with <provider> tag:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.devexchanges.contentproviderwithsqlitedb">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:multiprocess="true"
            android:authorities="info.devexchanges.contentprovider.CustomContentProvider"
            android:name="info.devexchanges.contentproviderwithsqlitedb.CustomContentProvider" />
    </application>

</manifest>

Usage on User Interface

    Over here, we have built the content provider with a database. The next part is declaring how to use on UI (activities, fragments or service,...). I will make an Activity which show all table records by a ListView and in it, you can add a new record to database table or remove any item from database by clicking delete button on each row. All access from UI to database through content provider URI.
    About adding a new record, you can use ContentValues by this code:
//Adding new record to database with Content provider
ContentValues values = new ContentValues();
values.put(DBHelper.COL_NAME, getText(txtName));
values.put(DBHelper.COL_JOB, getText(txtJob));
getContentResolver().insert(CustomContentProvider.CONTENT_URI, values);

    Fetching all records from table with ContentResolver and Cursor and store them to an ArrayList:

        Cursor cursor = getContentResolver().query(CustomContentProvider.CONTENT_URI, null, null, null, null);

        if (!cursor.moveToFirst()) {
            Toast.makeText(this, " no record yet!", Toast.LENGTH_SHORT).show();
        } else {
            do {
                String name = cursor.getString(cursor.getColumnIndex(DBHelper.COL_NAME));
                String job = cursor.getString(cursor.getColumnIndex(DBHelper.COL_JOB));

                //Loading to arraylist to set adapter data for ListView
                Friend friend = new Friend(name, job);
                friendList.add(friend);

            } while (cursor.moveToNext());
        }

    And deleting an exist record:

                //deleting a record in database table based on "name"
                String selection = DBHelper.COL_NAME + " = \"" + friend.getName() + "\"";
                int rowsDeleted = activity.getContentResolver().delete(CustomContentProvider.CONTENT_URI, selection, null);

                if (rowsDeleted > 0) {
                    Toast.makeText(activity, "Deleted!", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();

    Full code of this Activity:
MainActivity.java
package info.devexchanges.contentproviderwithsqlitedb;

import android.app.Dialog;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private List<Friend> friendList;
    private ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.list);
        friendList = new ArrayList<>();

        //set Listview adapter
        adapter = new ListViewAdapter(this, R.layout.item_listview, friendList);
        listView.setAdapter(adapter);
        showAllFriends();
    }

    public void showAllFriends() {
        friendList.clear(); //clear old arraylist data first
        Cursor cursor = getContentResolver().query(CustomContentProvider.CONTENT_URI, null, null, null, null);

        if (!cursor.moveToFirst()) {
            Toast.makeText(this, " no record yet!", Toast.LENGTH_SHORT).show();
        } else {
            do {
                String name = cursor.getString(cursor.getColumnIndex(DBHelper.COL_NAME));
                String job = cursor.getString(cursor.getColumnIndex(DBHelper.COL_JOB));

                //Loading to arraylist to set adapter data for ListView
                Friend friend = new Friend(name, job);
                friendList.add(friend);

            } while (cursor.moveToNext());
        }
        adapter.notifyDataSetChanged();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
            case R.id.add:
                showAddingDialog();
                break;

            default:
                break;

        }
        return super.onOptionsItemSelected(item);
    }

    private void showAddingDialog() {
        // custom dialog
        final Dialog dialog = new Dialog(this);
        dialog.setContentView(R.layout.layout_dialog_add);
        dialog.setTitle("Adding a new friend");

        final EditText txtName = (EditText) dialog.findViewById(R.id.name);
        final EditText txtJob = (EditText) dialog.findViewById(R.id.job);

        Button btnAdd = (Button) dialog.findViewById(R.id.btn_ok);
        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!hasText(txtJob) || !hasText(txtName)) {
                    Toast.makeText(getBaseContext(), "Please input full information...", Toast.LENGTH_SHORT).show();
                } else {
                    //Adding new record to database with Content provider
                    // Add a new birthday record
                    ContentValues values = new ContentValues();
                    values.put(DBHelper.COL_NAME, getText(txtName));
                    values.put(DBHelper.COL_JOB, getText(txtJob));
                    getContentResolver().insert(CustomContentProvider.CONTENT_URI, values);

                    Toast.makeText(getBaseContext(), "Inserted!", Toast.LENGTH_SHORT).show();
                    //reloading data
                    showAllFriends();
                    //dismiss dialog after adding process
                    dialog.dismiss();
                }
            }
        });

        dialog.show();
    }

    private boolean hasText(TextView textView) {
        if (textView.getText().toString().trim().equals("")) {
            return false;
        } else return true;
    }

    private String getText(TextView textView) {
        return textView.getText().toString().trim();
    }
}

    It's layout (only contains a ListView):
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

    Customizing a ListView adapter based on ArrayAdapter (the deletion code I put here):
ListViewAdapter.java
package info.devexchanges.contentproviderwithsqlitedb;

import android.app.Activity;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

public class ListViewAdapter extends ArrayAdapter<Friend> {

    private MainActivity activity;

    public ListViewAdapter(MainActivity context, int resource, List<Friend> objects) {
        super(context, resource, objects);
        this.activity = context;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.item_listview, parent, false);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        //set data to views
        holder.job.setText(getItem(position).getJob());
        holder.name.setText(getItem(position).getName());

        holder.btnDel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showAlertDialog(getItem(position));
            }
        });

        return convertView;
    }

    public void showAlertDialog(final Friend friend) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setTitle("Delete Confirm");
        builder.setCancelable(true);
        builder.setMessage("Are you sure?");
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                //deleting a record in database table based on "name"
                String selection = DBHelper.COL_NAME + " = \"" + friend.getName() + "\"";
                int rowsDeleted = activity.getContentResolver().delete(CustomContentProvider.CONTENT_URI, selection, null);

                if (rowsDeleted > 0) {
                    Toast.makeText(activity, "Deleted!", Toast.LENGTH_SHORT).show();

                    //reloading data
                    activity.showAllFriends();
                } else {
                    Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
                }
            }
        });
        builder.setNegativeButton("Cancel", null);

        builder.show();
    }

    private class ViewHolder {
        private TextView name;
        private TextView job;
        private ImageView btnDel;

        public ViewHolder(View v) {
            name = (TextView) v.findViewById(R.id.name);
            job = (TextView) v.findViewById(R.id.job);
            btnDel = (ImageView) v.findViewById(R.id.btn_del);
        }
    }
}

Some necessary files

    Layout for each ListView item:
item_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/app_name"
        android:textColor="@android:color/holo_blue_dark"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/job"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/name"
        android:text="@string/app_name"
        android:textColor="@android:color/holo_green_dark"
        android:textStyle="italic" />

    <ImageView
        android:id="@+id/btn_del"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:contentDescription="@string/app_name"
        android:src="@drawable/delete" />

</RelativeLayout>

    The POJO class for this project:
Friend.java
package info.devexchanges.contentproviderwithsqlitedb;

public class Friend {

    private String name;
    private String job;

    public Friend(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName() {
        return name;
    }

    public String getJob() {
        return job;
    }
}

    A dialog layout, use in adding the new record above:
layout_dialog_add.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="@dimen/activity_horizontal_margin">

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Put a name"
        android:inputType="textPersonName" />

    <EditText
        android:id="@+id/job"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Put a job"
        android:inputType="textPersonName" />

    <Button
        android:id="@+id/btn_ok"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add" />

</LinearLayout>

Run the application

    After running this project, we'll have this output (a list of records):
    When user click on (+) button, a dialog will appear to input the new record information:

    After inserting successful:

    When clicking the delete button at any row, a confirm action dialog appear:
    And this is the Toast notice after delete a record successful:

Conclusions

   This tutorial has taught you not only how to create a SQLite database and wrap it inside of a content provider, but also how straightforward it is to use a content provider to populate a ListView control. In this, I have not provide the way to update (edit) a record in database table, readers can find out yourself! I hope you’ve enjoyed this tutorial and finally, you can get full project code on @Github by click the button below.




Android Basic Training Course: Using Content Provider

    Content Provider is a 4th fundamental component of an Android app after Activity, Service and Intent. A Content Provider component supplies data from one application to others on request. Such requests are handled by the methods of the ContentResolver class. A content provider can use different ways to store it's data and the data can be stored in a database, in files, or even over a network.
    There are 2 types of Content Provider in Android: system (standard)  and user-defined Content Provider. All these standard Providers are defined in the package android.provider. The following table lists the standard providers and what they are used for:

Provider
Since    
Usage
Browser
SDK 1
Manages your web-searches, bookmarks and browsing-history.
CalendarContract
SDK 14
Manages the calendars on the user’s device.
CallLog
SDK 1
Keeps track of your call history.
Contacts
SDK 1
The old and deprecated content provider for managing contacts. You should only use this provider if you need to support an SDK prior to SDK 5!
ContactsContract
SDK 5
Deals with all aspects of contact management. Supersedes the Contacts-content provider.
MediaStore
SDK 1
The content provider responsible for all your media files like music, video and pictures.
Settings
SDK 1
Manages all global settings of your device.
UserDictionary
SDK 3
Keeps track of words you add to the default dictionary.
    In this post, I will present using the standard Content Provider in Android OS, with another one, you'll learn in next post.
    DEMO VIDEO:


Content URIs

    The most important concept to understand when dealing with content providers is the content URI. Whenever you want to access data from a content provider you have to specify a URI. URIs for content providers look like this:
<prefix>://<authority>/<data_type>/<id>
    Detail of these parts of the URI:
  • prefix: always is content:// 
  • authority: this specifies the name of the content provider, for example contacts, browser etc. For user-defined ContentProviders, this could be the fully declared name, such as info.devexchanges.contentprovider.
  • data_type: this indicates the type of data that this particular Provider provides. For example, if you are getting all the bookmarks from the Browser ContentProvider, then the data path would be people and URI would look like this content://browser/bookmarks.
  • id: the specific record requested.
    To query data in ContentProviders, we can use CursorLoader or ContentResolver.

CursorLoader

    A CursorLoader can run an asynchronous query in the background against a ContentProvider, and returns the results to Activity from which it was called. This allows the Activity to continue to interact with the user while the query is ongoing.
    When using it, declaring an instance first and make it load data in background thread, the code below will get all Media objects in Provider:
        CursorLoader loader = new CursorLoader(this, Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
        Cursor c = loader.loadInBackground();
    Detail of these parts in CursorLoader constructor:
  • Context: usually is the current Activity (use with "this").
  • URI: The URI of the object(s) to access. This is the only argument that must not be null, for example:  Media.EXTERNAL_CONTENT_URI.
  •  projection: a String array indicates which columns/attributes of the objects you want to access
  • selection: the selection clause, can usually set null.
  • selectionArgs: the selection argument, can usually set null.
  • sortOrder: sorting order for data, default is null.
    After this process, data will available in a Cursor instance, we can get it for our own aim.

ContentResolver

    The 2nd way to request data in ContentProvider is using ContentResolver. The ContentResolver decides which provider to use based on the authority part of the URI. As you will see in the third part of this mini-series a content provider must provide its authority within the manifest file. From these entries Android creates a mapping between the authorities and the corresponding ContentProvider implementations to use.
    You can get the ContentResolver object by calling getContentResolver() on the Context object (usually is current Activity or Service). Like CursorLoader in initializing, this code is use to get all Browser bookmarks:
Cursor c = getContentResolver().query(Browser.BOOKMARKS_URI, projection, null, null, null);
    The return value of the query method is a Cursor instance, too. Cursors are important resources that have to be closed whenever you have no more use for them – otherwise you keep valuable resources from being released.

Sample Project

    Of course, thousand document lines are not good as an example. In this project, I will access Media, Call Log and Browser bookmarks and save data to an ArrayList and displaying them by a ListView.
    Access the Browser ContentProvider, getting all bookmarks and parse data by using ContentResolver like this:
public void accessBookmarks() {
        Toast.makeText(this, "Show all Bookmarks", Toast.LENGTH_SHORT).show();
        String[] projection = {
                Browser.BookmarkColumns.TITLE,
                Browser.BookmarkColumns.URL,
        };
        Cursor c = getContentResolver().query(Browser.BOOKMARKS_URI, projection, null, null, null);
        assert c != null;
        c.moveToFirst(); // move Cursor to the first entity
        arrayList.clear(); // reset data before querying

        while (!c.isAfterLast()) {
            int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE);
            if (!c.getString(titleIndex).equals("")) {
                arrayList.add(c.getString(titleIndex));
            }
            c.moveToNext(); //go to next entity for loop
        }
        c.close();
        setListViewAdapter();
    }
    Output for this result, bookmarks will available in ListView:
    Absolutely similar with above case, get all Media from Media Store by CursorLoader:
 public void accessMediaStore() {
        Toast.makeText(this, "Show all Media Objects!", Toast.LENGTH_SHORT).show();
        String[] projection = {
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.DATE_ADDED,
                MediaStore.MediaColumns.MIME_TYPE
        };
        CursorLoader loader = new CursorLoader(this, Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
        Cursor c = loader.loadInBackground();
        c.moveToFirst();
        arrayList.clear();

        while (!c.isAfterLast()) {
            String mediaName = MediaStore.MediaColumns.DISPLAY_NAME;
            int nameIndex = c.getColumnIndex(mediaName);
            arrayList.add(c.getString(nameIndex));
            c.moveToNext();
        }

        c.close();
        setListViewAdapter();
    }
    Output result:
    And this code will getting all calls history in Call Log:
@SuppressWarnings("PointlessBooleanExpression")
    public void accessTheCallLog() {
        Toast.makeText(this, "Get all Call logs", Toast.LENGTH_SHORT).show();
        String[] projection = new String[]{Calls.DATE, Calls.NUMBER, Calls.DURATION};

        Cursor c = getContentResolver().query(CallLog.Calls.CONTENT_URI, projection, null, null, null);

        assert c != null;
        arrayList.clear();
        c.moveToFirst();

        while (!c.isAfterLast()) {
            String phoneNumber = Calls.NUMBER;
            int phoneIndex = c.getColumnIndex(phoneNumber);
            arrayList.add(c.getString(phoneIndex));
            c.moveToNext();
        }
        c.close();
        setListViewAdapter();
    }
    Full Activity code:
package info.devexchanges.contentprovidersample;

import android.os.Bundle;
import android.provider.Browser;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.Media;
import android.content.CursorLoader;
import android.database.Cursor;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends ActionBarActivity implements View.OnClickListener {

    private View btnGetCallLogs;
    private View btnGetBookmarks;
    private View btnGetMedia;
    private ArrayList<String> arrayList;
    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnGetBookmarks = findViewById(R.id.btn_get_bookmark);
        btnGetCallLogs = findViewById(R.id.btn_get_call_log);
        btnGetMedia = findViewById(R.id.btn_get_media);
        listView = (ListView) findViewById(R.id.list_view);

        btnGetMedia.setOnClickListener(this);
        btnGetCallLogs.setOnClickListener(this);
        btnGetBookmarks.setOnClickListener(this);

        arrayList = new ArrayList<>();
    }

    @SuppressWarnings("PointlessBooleanExpression")
    public void accessTheCallLog() {
        Toast.makeText(this, "Get all Call logs", Toast.LENGTH_SHORT).show();
        String[] projection = new String[]{Calls.DATE, Calls.NUMBER, Calls.DURATION};

        Cursor c = getContentResolver().query(CallLog.Calls.CONTENT_URI, projection, null, null, null);

        assert c != null;
        arrayList.clear();
        c.moveToFirst();

        while (!c.isAfterLast()) {
            String phoneNumber = Calls.NUMBER;
            int phoneIndex = c.getColumnIndex(phoneNumber);
            arrayList.add(c.getString(phoneIndex));
            c.moveToNext();
        }
        c.close();
        setListViewAdapter();
    }


    public void accessMediaStore() {
        Toast.makeText(this, "Show all Media Objects!", Toast.LENGTH_SHORT).show();
        String[] projection = {
                MediaStore.MediaColumns.DISPLAY_NAME,
                MediaStore.MediaColumns.DATE_ADDED,
                MediaStore.MediaColumns.MIME_TYPE
        };
        CursorLoader loader = new CursorLoader(this, Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
        Cursor c = loader.loadInBackground();
        c.moveToFirst();
        arrayList.clear();

        while (!c.isAfterLast()) {
            String mediaName = MediaStore.MediaColumns.DISPLAY_NAME;
            int nameIndex = c.getColumnIndex(mediaName);
            arrayList.add(c.getString(nameIndex));
            c.moveToNext();
        }

        c.close();
        setListViewAdapter();
    }

    private void setListViewAdapter() {
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, arrayList);
        listView.setAdapter(adapter);
    }

    public void accessBookmarks() {
        Toast.makeText(this, "Show all Bookmarks", Toast.LENGTH_SHORT).show();
        String[] projection = {
                Browser.BookmarkColumns.TITLE,
                Browser.BookmarkColumns.URL,
        };
        Cursor c = getContentResolver().query(Browser.BOOKMARKS_URI, projection, null, null, null);
        assert c != null;
        c.moveToFirst();
        arrayList.clear();

        while (!c.isAfterLast()) {
            int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE);
            if (!c.getString(titleIndex).equals("")) {
                arrayList.add(c.getString(titleIndex));
            }
            c.moveToNext();
        }
        c.close();
        setListViewAdapter();
    }

    @Override
    public void onClick(View v) {
        if (v == btnGetBookmarks) {
            accessBookmarks();
        } else if (v == btnGetCallLogs) {
            accessTheCallLog();
        } else if (v == btnGetMedia) {
            accessMediaStore();
        } else {
            //????
        }
    }
}

Need more example?

    Through this post, you can learn about using system ContentProvider. Up to the next chapter, I will guide to creating your own ones. In this sample project, I only provide the code to get data from ContentProvider, and of course, you can update, delete it. I also have a post about access and configuring our Contact (a system ContentProvider) here, you can find out the way to do after query to Provider content (edit, delete, add new Contact).

References

References to official docs:
- ContentProvider: http://developer.android.com/reference/android/content/ContentProvider.html
- ContentResolver: http://developer.android.com/reference/android/content/ContentResolver.html
- CursorLoader: http://developer.android.com/reference/android/content/CursorLoader.html
- Cursor: http://developer.android.com/reference/android/database/Cursor.html
Accessing and Configuring Device Contacts post: http://www.devexchanges.info/2015/03/android-simple-contentprovider-and.html



Android simple ContentProvider and ContentResolver example

Android simple ContentProvider and ContentResolver example

In Android security model, one application cannot directly access (read/write) other application's data. Every application has its own id data directory and own protected memory area.
ContentProvider is the best way to share data across applications. Content provider is a set of data wrapped up in a custom API to read and write. Applications/Processes have to register themselves as a provider of data. Other applications can request Android to read/write that data through a fixed API.
Content provider API adheres to CRUD principle.
Examples for ContentProvider are Contacts-that exposes user information to other applications, Media store-Allows other applications to access,store media files.
Okey, in this tip, I present the way to use system Content Provider (contacts content provider) to get, add new and delete contacts in Android device through an Uri.

1. Launch Eclipse and start a new Android Project (min-sdk I used is 14).
2. Create a layout (activity_main.xml):
3. Create a simple model (ContactInfor) to use in project:
4. Give a custom adapter for ListView in our activity (ContactAdapter - extends ArrayAdapter):
5. Now, in running activity (ContactActivity), we provide 3 methods to add a new contact, delete an exist contact and get all contacts in our device. By performing this, ContentResolver was used to access system contacts information.
- Add new contact method:
- Get all contacts in device:
- Delete an exist contact (I call this method in ContactAdapter) and update contacts list:
6. Add some necessary methods, we have full ActivityContact code:
7. Open your AndroidManifest.xml file and add Write/Read Contact permission:
8. Some screens after running app:

pic name pic name pic name pic name
(sorry for having ads)