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; + } +}