/* * 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<Cursor> { 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<Cursor> 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<Cursor> 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<Cursor> 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; } }