diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml
index ff44b0e95..960bdef9d 100644
--- a/samples/ApiDemos/AndroidManifest.xml
+++ b/samples/ApiDemos/AndroidManifest.xml
@@ -1168,6 +1168,13 @@
+
+
+
+
+
+
+
diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml
index c9c347eab..ff4b89871 100644
--- a/samples/ApiDemos/res/values/strings.xml
+++ b/samples/ApiDemos/res/values/strings.xml
@@ -450,6 +450,8 @@
Pick a Phone
Pick an Address
+ Content/Provider/Changed Contacts
+
Content/Packages/Install Apk
diff --git a/samples/ApiDemos/src/com/example/android/apis/content/ChangedContacts.java b/samples/ApiDemos/src/com/example/android/apis/content/ChangedContacts.java
new file mode 100644
index 000000000..412a3c6dc
--- /dev/null
+++ b/samples/ApiDemos/src/com/example/android/apis/content/ChangedContacts.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.example.android.apis.content;
+
+import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.Loader;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CursorAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Demonstrates selecting contacts that have changed since a certain time.
+ */
+public class ChangedContacts extends Activity implements LoaderManager.LoaderCallbacks {
+
+ private static final String CLASS = ChangedContacts.class.getSimpleName();
+
+ private static final String PREF_KEY_CHANGE = "timestamp_change";
+ private static final String PREF_KEY_DELETE = "timestamp_delete";
+
+ private static final int ID_CHANGE_LOADER = 1;
+ private static final int ID_DELETE_LOADER = 2;
+
+ /**
+ * To see this in action, "clear data" for the contacts storage app in the system settings.
+ * Then come into this app and hit any of the delta buttons. This will cause the contacts
+ * database to be re-created.
+ */
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Toast toast = Toast.makeText(context, "Contacts database created.", Toast.LENGTH_SHORT);
+ toast.show();
+ }
+ };
+
+ private DeleteAdapter mDeleteAdapter;
+ private ChangeAdapter mChangeAdapter;
+ private long mSearchTime;
+ private TextView mDisplayView;
+ private ListView mList;
+ private Button mDeleteButton;
+ private Button mChangeButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mDeleteAdapter = new DeleteAdapter(this, null, 0);
+ mChangeAdapter = new ChangeAdapter(this, null, 0);
+
+ LinearLayout main = new LinearLayout(this);
+ main.setOrientation(LinearLayout.VERTICAL);
+
+ mChangeButton = new Button(this);
+ mChangeButton.setText("Changed since " + getLastTimestamp(0, PREF_KEY_CHANGE));
+ mChangeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ changeClick();
+ }
+ });
+
+ mDeleteButton = new Button(this);
+ mDeleteButton.setText("Deleted since " + getLastTimestamp(0, PREF_KEY_DELETE));
+ mDeleteButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ deleteClick();
+ }
+ });
+
+ main.addView(mChangeButton);
+ main.addView(mDeleteButton);
+
+ mDisplayView = new TextView(this);
+ mDisplayView.setPadding(5, 5, 5, 5);
+ main.addView(mDisplayView);
+
+ mList = new ListView(this);
+ mList.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
+ main.addView(mList);
+
+ setContentView(main);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ContactsContract.Intents.CONTACTS_DATABASE_CREATED);
+ registerReceiver(mReceiver, filter);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mReceiver);
+ }
+
+ private void changeClick() {
+ mChangeAdapter.swapCursor(null);
+ LoaderManager manager = getLoaderManager();
+ manager.destroyLoader(ID_DELETE_LOADER);
+ manager.restartLoader(ID_CHANGE_LOADER, null, this);
+ }
+
+ private void deleteClick() {
+ mChangeAdapter.swapCursor(null);
+ LoaderManager manager = getLoaderManager();
+ manager.destroyLoader(ID_CHANGE_LOADER);
+ manager.restartLoader(ID_DELETE_LOADER, null, this);
+ }
+
+ private void saveLastTimestamp(long time, String key) {
+ SharedPreferences pref = getSharedPreferences(CLASS, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = pref.edit();
+ editor.putLong(key, time);
+ editor.commit();
+ }
+
+ private long getLastTimestamp(long time, String key) {
+ SharedPreferences pref = getSharedPreferences(CLASS, Context.MODE_PRIVATE);
+ return pref.getLong(key, time);
+ }
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ switch(id) {
+ case ID_CHANGE_LOADER:
+ return getChangeLoader();
+ case ID_DELETE_LOADER:
+ return getDeleteLoader();
+ }
+ return null;
+ }
+
+ private CursorLoader getChangeLoader() {
+ String[] projection = new String[]{
+ ContactsContract.Data._ID,
+ ContactsContract.Data.CONTACT_ID,
+ ContactsContract.Data.DISPLAY_NAME,
+ ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP
+ };
+
+ mSearchTime = getLastTimestamp(0, PREF_KEY_CHANGE);
+
+ String selection = ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?";
+ String[] bindArgs = new String[]{mSearchTime + ""};
+ return new CursorLoader(this, ContactsContract.Data.CONTENT_URI, projection,
+ selection, bindArgs, ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP
+ + " desc, " + ContactsContract.Data.CONTACT_ID + " desc");
+ }
+
+ private CursorLoader getDeleteLoader() {
+ String[] projection = new String[]{
+ ContactsContract.DeletedContacts.CONTACT_ID,
+ ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP
+ };
+
+ mSearchTime = getLastTimestamp(0, PREF_KEY_DELETE);
+
+ String selection = ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?";
+ String[] bindArgs = new String[]{mSearchTime + ""};
+ return new CursorLoader(this, ContactsContract.DeletedContacts.CONTENT_URI, projection,
+ selection, bindArgs, ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP +
+ " desc");
+ }
+
+ @Override
+ public void onLoadFinished(Loader cursorLoader, Cursor data) {
+ long timestamp = 0;
+
+
+ switch (cursorLoader.getId()) {
+ case ID_CHANGE_LOADER:
+ mDisplayView.setText(data.getCount() + " change(s) since " + mSearchTime);
+ mList.setAdapter(mChangeAdapter);
+ mChangeAdapter.swapCursor(data);
+
+ // Save the largest timestamp returned. Only need the first one due to the sort
+ // order.
+ if (data.moveToNext()) {
+ timestamp = data.getLong(data.getColumnIndex(
+ ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP));
+ data.moveToPrevious();
+ }
+ if (timestamp > 0) {
+ saveLastTimestamp(timestamp, PREF_KEY_CHANGE);
+ mChangeButton.setText("Changed since " + timestamp);
+ }
+ break;
+ case ID_DELETE_LOADER:
+ mDisplayView.setText(data.getCount() + " delete(s) since " + mSearchTime);
+ mList.setAdapter(mDeleteAdapter);
+ mDeleteAdapter.swapCursor(new DeleteCursorWrapper(data));
+ if (data.moveToNext()) {
+ timestamp = data.getLong(data.getColumnIndex(
+ ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP));
+ data.moveToPrevious();
+ }
+ if (timestamp > 0) {
+ saveLastTimestamp(timestamp, PREF_KEY_DELETE);
+ mDeleteButton.setText("Deleted since " + timestamp);
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader cursorLoader) {
+ mDisplayView.setText("");
+ switch (cursorLoader.getId()) {
+ case ID_CHANGE_LOADER:
+ mChangeAdapter.swapCursor(null);
+ break;
+ case ID_DELETE_LOADER:
+ mDeleteAdapter.swapCursor(null);
+ break;
+ }
+ }
+
+ private class DeleteCursorWrapper extends CursorWrapper {
+
+ /**
+ * Creates a cursor wrapper.
+ *
+ * @param cursor The underlying cursor to wrap.
+ */
+ public DeleteCursorWrapper(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) {
+ if (columnName.equals("_id")) {
+ return super.getColumnIndex(ContactsContract.DeletedContacts.CONTACT_ID);
+ }
+ return super.getColumnIndex(columnName);
+ }
+ }
+
+ private static class DeleteAdapter extends CursorAdapter {
+
+ private Context mContext;
+
+ public DeleteAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+ this.mContext = context;
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ LinearLayout item = new LinearLayout(mContext);
+ item.addView(buildText(context));
+ item.addView(buildText(context));
+ return item;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ LinearLayout item = (LinearLayout) view;
+ String id = cursor.getString(cursor.getColumnIndex(
+ ContactsContract.DeletedContacts.CONTACT_ID));
+ String timestamp = cursor.getString(cursor.getColumnIndex(
+ ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP));
+
+ setText(item.getChildAt(0), id);
+ setText(item.getChildAt(1), timestamp);
+ }
+ }
+
+ private static class ChangeAdapter extends CursorAdapter {
+
+ private Context mContext;
+
+ public ChangeAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+ mContext = context;
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ LinearLayout item = new LinearLayout(mContext);
+ item.addView(buildText(context));
+ item.addView(buildText(context));
+ item.addView(buildText(context));
+ return item;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ LinearLayout item = (LinearLayout) view;
+
+ String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID));
+ String name = cursor.getString(cursor.getColumnIndex(
+ ContactsContract.Data.DISPLAY_NAME));
+ String timestamp = cursor.getString(cursor.getColumnIndex(
+ ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP));
+
+ setText(item.getChildAt(0), id);
+ setText(item.getChildAt(1), name);
+ setText(item.getChildAt(2), timestamp);
+ }
+ }
+
+ private static void setText(View view, String value) {
+ TextView text = (TextView) view;
+ text.setText(value);
+ }
+
+ private static TextView buildText(Context context) {
+ TextView view = new TextView(context);
+ view.setPadding(3, 3, 3, 3);
+ return view;
+ }
+}