Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2008 Esmertec AG.
      3  * Copyright (C) 2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mms.ui;
     19 
     20 import com.android.mms.LogTag;
     21 import com.android.mms.R;
     22 import com.android.mms.data.Contact;
     23 import com.android.mms.data.ContactList;
     24 import com.android.mms.data.Conversation;
     25 import com.android.mms.transaction.MessagingNotification;
     26 import com.android.mms.transaction.SmsRejectedReceiver;
     27 import com.android.mms.util.DraftCache;
     28 import com.android.mms.util.Recycler;
     29 import com.google.android.mms.pdu.PduHeaders;
     30 import android.database.sqlite.SqliteWrapper;
     31 
     32 import android.app.AlertDialog;
     33 import android.app.ListActivity;
     34 import android.content.AsyncQueryHandler;
     35 import android.content.ContentResolver;
     36 import android.content.Context;
     37 import android.content.DialogInterface;
     38 import android.content.Intent;
     39 import android.content.SharedPreferences;
     40 import android.content.DialogInterface.OnClickListener;
     41 import android.content.res.Configuration;
     42 import android.database.Cursor;
     43 import android.database.sqlite.SQLiteException;
     44 import android.database.sqlite.SQLiteFullException;
     45 import android.os.Bundle;
     46 import android.os.Handler;
     47 import android.preference.PreferenceManager;
     48 import android.provider.ContactsContract;
     49 import android.provider.ContactsContract.Contacts;
     50 import android.provider.Telephony.Mms;
     51 import android.util.Log;
     52 import android.view.ContextMenu;
     53 import android.view.KeyEvent;
     54 import android.view.LayoutInflater;
     55 import android.view.Menu;
     56 import android.view.MenuItem;
     57 import android.view.View;
     58 import android.view.Window;
     59 import android.view.ContextMenu.ContextMenuInfo;
     60 import android.view.View.OnCreateContextMenuListener;
     61 import android.view.View.OnKeyListener;
     62 import android.widget.AdapterView;
     63 import android.widget.CheckBox;
     64 import android.widget.ListView;
     65 import android.widget.TextView;
     66 
     67 /**
     68  * This activity provides a list view of existing conversations.
     69  */
     70 public class ConversationList extends ListActivity
     71             implements DraftCache.OnDraftChangedListener {
     72     private static final String TAG = "ConversationList";
     73     private static final boolean DEBUG = false;
     74     private static final boolean LOCAL_LOGV = DEBUG;
     75 
     76     private static final int THREAD_LIST_QUERY_TOKEN       = 1701;
     77     public static final int DELETE_CONVERSATION_TOKEN      = 1801;
     78     public static final int HAVE_LOCKED_MESSAGES_TOKEN     = 1802;
     79     private static final int DELETE_OBSOLETE_THREADS_TOKEN = 1803;
     80 
     81     // IDs of the main menu items.
     82     public static final int MENU_COMPOSE_NEW          = 0;
     83     public static final int MENU_SEARCH               = 1;
     84     public static final int MENU_DELETE_ALL           = 3;
     85     public static final int MENU_PREFERENCES          = 4;
     86 
     87     // IDs of the context menu items for the list of conversations.
     88     public static final int MENU_DELETE               = 0;
     89     public static final int MENU_VIEW                 = 1;
     90     public static final int MENU_VIEW_CONTACT         = 2;
     91     public static final int MENU_ADD_TO_CONTACTS      = 3;
     92 
     93     private ThreadListQueryHandler mQueryHandler;
     94     private ConversationListAdapter mListAdapter;
     95     private CharSequence mTitle;
     96     private SharedPreferences mPrefs;
     97     private Handler mHandler;
     98     private boolean mNeedToMarkAsSeen;
     99 
    100     static private final String CHECKED_MESSAGE_LIMITS = "checked_message_limits";
    101 
    102     @Override
    103     protected void onCreate(Bundle savedInstanceState) {
    104         super.onCreate(savedInstanceState);
    105 
    106         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
    107         setContentView(R.layout.conversation_list_screen);
    108 
    109         mQueryHandler = new ThreadListQueryHandler(getContentResolver());
    110 
    111         ListView listView = getListView();
    112         LayoutInflater inflater = LayoutInflater.from(this);
    113         ConversationListItem headerView = (ConversationListItem)
    114                 inflater.inflate(R.layout.conversation_list_item, listView, false);
    115         headerView.bind(getString(R.string.new_message),
    116                 getString(R.string.create_new_message));
    117         listView.addHeaderView(headerView, null, true);
    118 
    119         listView.setOnCreateContextMenuListener(mConvListOnCreateContextMenuListener);
    120         listView.setOnKeyListener(mThreadListKeyListener);
    121 
    122         initListAdapter();
    123 
    124         mTitle = getString(R.string.app_label);
    125 
    126         mHandler = new Handler();
    127         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    128         boolean checkedMessageLimits = mPrefs.getBoolean(CHECKED_MESSAGE_LIMITS, false);
    129         if (DEBUG) Log.v(TAG, "checkedMessageLimits: " + checkedMessageLimits);
    130         if (!checkedMessageLimits || DEBUG) {
    131             runOneTimeStorageLimitCheckForLegacyMessages();
    132         }
    133     }
    134 
    135     private final ConversationListAdapter.OnContentChangedListener mContentChangedListener =
    136         new ConversationListAdapter.OnContentChangedListener() {
    137         public void onContentChanged(ConversationListAdapter adapter) {
    138             startAsyncQuery();
    139         }
    140     };
    141 
    142     private void initListAdapter() {
    143         mListAdapter = new ConversationListAdapter(this, null);
    144         mListAdapter.setOnContentChangedListener(mContentChangedListener);
    145         setListAdapter(mListAdapter);
    146         getListView().setRecyclerListener(mListAdapter);
    147     }
    148 
    149     /**
    150      * Checks to see if the number of MMS and SMS messages are under the limits for the
    151      * recycler. If so, it will automatically turn on the recycler setting. If not, it
    152      * will prompt the user with a message and point them to the setting to manually
    153      * turn on the recycler.
    154      */
    155     public synchronized void runOneTimeStorageLimitCheckForLegacyMessages() {
    156         if (Recycler.isAutoDeleteEnabled(this)) {
    157             if (DEBUG) Log.v(TAG, "recycler is already turned on");
    158             // The recycler is already turned on. We don't need to check anything or warn
    159             // the user, just remember that we've made the check.
    160             markCheckedMessageLimit();
    161             return;
    162         }
    163         new Thread(new Runnable() {
    164             public void run() {
    165                 if (Recycler.checkForThreadsOverLimit(ConversationList.this)) {
    166                     if (DEBUG) Log.v(TAG, "checkForThreadsOverLimit TRUE");
    167                     // Dang, one or more of the threads are over the limit. Show an activity
    168                     // that'll encourage the user to manually turn on the setting. Delay showing
    169                     // this activity until a couple of seconds after the conversation list appears.
    170                     mHandler.postDelayed(new Runnable() {
    171                         public void run() {
    172                             Intent intent = new Intent(ConversationList.this,
    173                                     WarnOfStorageLimitsActivity.class);
    174                             startActivity(intent);
    175                         }
    176                     }, 2000);
    177                 } else {
    178                     if (DEBUG) Log.v(TAG, "checkForThreadsOverLimit silently turning on recycler");
    179                     // No threads were over the limit. Turn on the recycler by default.
    180                     runOnUiThread(new Runnable() {
    181                         public void run() {
    182                             SharedPreferences.Editor editor = mPrefs.edit();
    183                             editor.putBoolean(MessagingPreferenceActivity.AUTO_DELETE, true);
    184                             editor.commit();
    185                         }
    186                     });
    187                 }
    188                 // Remember that we don't have to do the check anymore when starting MMS.
    189                 runOnUiThread(new Runnable() {
    190                     public void run() {
    191                         markCheckedMessageLimit();
    192                     }
    193                 });
    194             }
    195         }).start();
    196     }
    197 
    198     /**
    199      * Mark in preferences that we've checked the user's message limits. Once checked, we'll
    200      * never check them again, unless the user wipe-data or resets the device.
    201      */
    202     private void markCheckedMessageLimit() {
    203         if (DEBUG) Log.v(TAG, "markCheckedMessageLimit");
    204         SharedPreferences.Editor editor = mPrefs.edit();
    205         editor.putBoolean(CHECKED_MESSAGE_LIMITS, true);
    206         editor.commit();
    207     }
    208 
    209     @Override
    210     protected void onNewIntent(Intent intent) {
    211         // Handle intents that occur after the activity has already been created.
    212         startAsyncQuery();
    213     }
    214 
    215     @Override
    216     protected void onStart() {
    217         super.onStart();
    218 
    219         MessagingNotification.cancelNotification(getApplicationContext(),
    220                 SmsRejectedReceiver.SMS_REJECTED_NOTIFICATION_ID);
    221 
    222         DraftCache.getInstance().addOnDraftChangedListener(this);
    223 
    224         mNeedToMarkAsSeen = true;
    225 
    226         startAsyncQuery();
    227 
    228         // We used to refresh the DraftCache here, but
    229         // refreshing the DraftCache each time we go to the ConversationList seems overly
    230         // aggressive. We already update the DraftCache when leaving CMA in onStop() and
    231         // onNewIntent(), and when we delete threads or delete all in CMA or this activity.
    232         // I hope we don't have to do such a heavy operation each time we enter here.
    233 
    234         // we invalidate the contact cache here because we want to get updated presence
    235         // and any contact changes. We don't invalidate the cache by observing presence and contact
    236         // changes (since that's too untargeted), so as a tradeoff we do it here.
    237         // If we're in the middle of the app initialization where we're loading the conversation
    238         // threads, don't invalidate the cache because we're in the process of building it.
    239         // TODO: think of a better way to invalidate cache more surgically or based on actual
    240         // TODO: changes we care about
    241         if (!Conversation.loadingThreads()) {
    242             Contact.invalidateCache();
    243         }
    244     }
    245 
    246     @Override
    247     protected void onStop() {
    248         super.onStop();
    249 
    250         DraftCache.getInstance().removeOnDraftChangedListener(this);
    251         mListAdapter.changeCursor(null);
    252     }
    253 
    254     public void onDraftChanged(final long threadId, final boolean hasDraft) {
    255         // Run notifyDataSetChanged() on the main thread.
    256         mQueryHandler.post(new Runnable() {
    257             public void run() {
    258                 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    259                     log("onDraftChanged: threadId=" + threadId + ", hasDraft=" + hasDraft);
    260                 }
    261                 mListAdapter.notifyDataSetChanged();
    262             }
    263         });
    264     }
    265 
    266     private void startAsyncQuery() {
    267         try {
    268             setTitle(getString(R.string.refreshing));
    269             setProgressBarIndeterminateVisibility(true);
    270 
    271             Conversation.startQueryForAll(mQueryHandler, THREAD_LIST_QUERY_TOKEN);
    272         } catch (SQLiteException e) {
    273             SqliteWrapper.checkSQLiteException(this, e);
    274         }
    275     }
    276 
    277     @Override
    278     public boolean onPrepareOptionsMenu(Menu menu) {
    279         menu.clear();
    280 
    281         menu.add(0, MENU_COMPOSE_NEW, 0, R.string.menu_compose_new).setIcon(
    282                 com.android.internal.R.drawable.ic_menu_compose);
    283 
    284         if (mListAdapter.getCount() > 0) {
    285             menu.add(0, MENU_DELETE_ALL, 0, R.string.menu_delete_all).setIcon(
    286                     android.R.drawable.ic_menu_delete);
    287         }
    288 
    289         menu.add(0, MENU_SEARCH, 0, android.R.string.search_go).
    290             setIcon(android.R.drawable.ic_menu_search).
    291             setAlphabeticShortcut(android.app.SearchManager.MENU_KEY);
    292 
    293         menu.add(0, MENU_PREFERENCES, 0, R.string.menu_preferences).setIcon(
    294                 android.R.drawable.ic_menu_preferences);
    295 
    296         return true;
    297     }
    298 
    299     @Override
    300     public boolean onSearchRequested() {
    301         startSearch(null, false, null /*appData*/, false);
    302         return true;
    303     }
    304 
    305     @Override
    306     public boolean onOptionsItemSelected(MenuItem item) {
    307         switch(item.getItemId()) {
    308             case MENU_COMPOSE_NEW:
    309                 createNewMessage();
    310                 break;
    311             case MENU_SEARCH:
    312                 onSearchRequested();
    313                 break;
    314             case MENU_DELETE_ALL:
    315                 // The invalid threadId of -1 means all threads here.
    316                 confirmDeleteThread(-1L, mQueryHandler);
    317                 break;
    318             case MENU_PREFERENCES: {
    319                 Intent intent = new Intent(this, MessagingPreferenceActivity.class);
    320                 startActivityIfNeeded(intent, -1);
    321                 break;
    322             }
    323             default:
    324                 return true;
    325         }
    326         return false;
    327     }
    328 
    329     @Override
    330     protected void onListItemClick(ListView l, View v, int position, long id) {
    331         if (LOCAL_LOGV) {
    332             Log.v(TAG, "onListItemClick: position=" + position + ", id=" + id);
    333         }
    334 
    335         if (position == 0) {
    336             createNewMessage();
    337         } else if (v instanceof ConversationListItem) {
    338             ConversationListItem headerView = (ConversationListItem) v;
    339             ConversationListItemData ch = headerView.getConversationHeader();
    340             openThread(ch.getThreadId());
    341         }
    342     }
    343 
    344     private void createNewMessage() {
    345         startActivity(ComposeMessageActivity.createIntent(this, 0));
    346     }
    347 
    348     private void openThread(long threadId) {
    349         startActivity(ComposeMessageActivity.createIntent(this, threadId));
    350     }
    351 
    352     public static Intent createAddContactIntent(String address) {
    353         // address must be a single recipient
    354         Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    355         intent.setType(Contacts.CONTENT_ITEM_TYPE);
    356         if (Mms.isEmailAddress(address)) {
    357             intent.putExtra(ContactsContract.Intents.Insert.EMAIL, address);
    358         } else {
    359             intent.putExtra(ContactsContract.Intents.Insert.PHONE, address);
    360             intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE,
    361                     ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
    362         }
    363         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
    364 
    365         return intent;
    366     }
    367 
    368     private final OnCreateContextMenuListener mConvListOnCreateContextMenuListener =
    369         new OnCreateContextMenuListener() {
    370         public void onCreateContextMenu(ContextMenu menu, View v,
    371                 ContextMenuInfo menuInfo) {
    372             Cursor cursor = mListAdapter.getCursor();
    373             if (cursor == null || cursor.getPosition() < 0) {
    374                 return;
    375             }
    376             Conversation conv = Conversation.from(ConversationList.this, cursor);
    377             ContactList recipients = conv.getRecipients();
    378             menu.setHeaderTitle(recipients.formatNames(","));
    379 
    380             AdapterView.AdapterContextMenuInfo info =
    381                 (AdapterView.AdapterContextMenuInfo) menuInfo;
    382             if (info.position > 0) {
    383                 menu.add(0, MENU_VIEW, 0, R.string.menu_view);
    384 
    385                 // Only show if there's a single recipient
    386                 if (recipients.size() == 1) {
    387                     // do we have this recipient in contacts?
    388                     if (recipients.get(0).existsInDatabase()) {
    389                         menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact);
    390                     } else {
    391                         menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts);
    392                     }
    393                 }
    394                 menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
    395             }
    396         }
    397     };
    398 
    399     @Override
    400     public boolean onContextItemSelected(MenuItem item) {
    401         Cursor cursor = mListAdapter.getCursor();
    402         if (cursor != null && cursor.getPosition() >= 0) {
    403             Conversation conv = Conversation.from(ConversationList.this, cursor);
    404             long threadId = conv.getThreadId();
    405             switch (item.getItemId()) {
    406             case MENU_DELETE: {
    407                 confirmDeleteThread(threadId, mQueryHandler);
    408                 break;
    409             }
    410             case MENU_VIEW: {
    411                 openThread(threadId);
    412                 break;
    413             }
    414             case MENU_VIEW_CONTACT: {
    415                 Contact contact = conv.getRecipients().get(0);
    416                 Intent intent = new Intent(Intent.ACTION_VIEW, contact.getUri());
    417                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
    418                 startActivity(intent);
    419                 break;
    420             }
    421             case MENU_ADD_TO_CONTACTS: {
    422                 String address = conv.getRecipients().get(0).getNumber();
    423                 startActivity(createAddContactIntent(address));
    424                 break;
    425             }
    426             default:
    427                 break;
    428             }
    429         }
    430         return super.onContextItemSelected(item);
    431     }
    432 
    433     @Override
    434     public void onConfigurationChanged(Configuration newConfig) {
    435         // We override this method to avoid restarting the entire
    436         // activity when the keyboard is opened (declared in
    437         // AndroidManifest.xml).  Because the only translatable text
    438         // in this activity is "New Message", which has the full width
    439         // of phone to work with, localization shouldn't be a problem:
    440         // no abbreviated alternate words should be needed even in
    441         // 'wide' languages like German or Russian.
    442 
    443         super.onConfigurationChanged(newConfig);
    444         if (DEBUG) Log.v(TAG, "onConfigurationChanged: " + newConfig);
    445     }
    446 
    447     /**
    448      * Start the process of putting up a dialog to confirm deleting a thread,
    449      * but first start a background query to see if any of the threads or thread
    450      * contain locked messages so we'll know how detailed of a UI to display.
    451      * @param threadId id of the thread to delete or -1 for all threads
    452      * @param handler query handler to do the background locked query
    453      */
    454     public static void confirmDeleteThread(long threadId, AsyncQueryHandler handler) {
    455         Conversation.startQueryHaveLockedMessages(handler, threadId,
    456                 HAVE_LOCKED_MESSAGES_TOKEN);
    457     }
    458 
    459     /**
    460      * Build and show the proper delete thread dialog. The UI is slightly different
    461      * depending on whether there are locked messages in the thread(s) and whether we're
    462      * deleting a single thread or all threads.
    463      * @param listener gets called when the delete button is pressed
    464      * @param deleteAll whether to show a single thread or all threads UI
    465      * @param hasLockedMessages whether the thread(s) contain locked messages
    466      * @param context used to load the various UI elements
    467      */
    468     public static void confirmDeleteThreadDialog(final DeleteThreadListener listener,
    469             boolean deleteAll,
    470             boolean hasLockedMessages,
    471             Context context) {
    472         View contents = View.inflate(context, R.layout.delete_thread_dialog_view, null);
    473         TextView msg = (TextView)contents.findViewById(R.id.message);
    474         msg.setText(deleteAll
    475                 ? R.string.confirm_delete_all_conversations
    476                         : R.string.confirm_delete_conversation);
    477         final CheckBox checkbox = (CheckBox)contents.findViewById(R.id.delete_locked);
    478         if (!hasLockedMessages) {
    479             checkbox.setVisibility(View.GONE);
    480         } else {
    481             listener.setDeleteLockedMessage(checkbox.isChecked());
    482             checkbox.setOnClickListener(new View.OnClickListener() {
    483                 public void onClick(View v) {
    484                     listener.setDeleteLockedMessage(checkbox.isChecked());
    485                 }
    486             });
    487         }
    488 
    489         AlertDialog.Builder builder = new AlertDialog.Builder(context);
    490         builder.setTitle(R.string.confirm_dialog_title)
    491             .setIcon(android.R.drawable.ic_dialog_alert)
    492         .setCancelable(true)
    493         .setPositiveButton(R.string.delete, listener)
    494         .setNegativeButton(R.string.no, null)
    495         .setView(contents)
    496         .show();
    497     }
    498 
    499     private final OnKeyListener mThreadListKeyListener = new OnKeyListener() {
    500         public boolean onKey(View v, int keyCode, KeyEvent event) {
    501             if (event.getAction() == KeyEvent.ACTION_DOWN) {
    502                 switch (keyCode) {
    503                     case KeyEvent.KEYCODE_DEL: {
    504                         long id = getListView().getSelectedItemId();
    505                         if (id > 0) {
    506                             confirmDeleteThread(id, mQueryHandler);
    507                         }
    508                         return true;
    509                     }
    510                 }
    511             }
    512             return false;
    513         }
    514     };
    515 
    516     public static class DeleteThreadListener implements OnClickListener {
    517         private final long mThreadId;
    518         private final AsyncQueryHandler mHandler;
    519         private final Context mContext;
    520         private boolean mDeleteLockedMessages;
    521 
    522         public DeleteThreadListener(long threadId, AsyncQueryHandler handler, Context context) {
    523             mThreadId = threadId;
    524             mHandler = handler;
    525             mContext = context;
    526         }
    527 
    528         public void setDeleteLockedMessage(boolean deleteLockedMessages) {
    529             mDeleteLockedMessages = deleteLockedMessages;
    530         }
    531 
    532         public void onClick(DialogInterface dialog, final int whichButton) {
    533             MessageUtils.handleReadReport(mContext, mThreadId,
    534                     PduHeaders.READ_STATUS__DELETED_WITHOUT_BEING_READ, new Runnable() {
    535                 public void run() {
    536                     int token = DELETE_CONVERSATION_TOKEN;
    537                     if (mThreadId == -1) {
    538                         Conversation.startDeleteAll(mHandler, token, mDeleteLockedMessages);
    539                         DraftCache.getInstance().refresh();
    540                     } else {
    541                         Conversation.startDelete(mHandler, token, mDeleteLockedMessages,
    542                                 mThreadId);
    543                         DraftCache.getInstance().setDraftState(mThreadId, false);
    544                     }
    545                 }
    546             });
    547         }
    548     }
    549 
    550     private final class ThreadListQueryHandler extends AsyncQueryHandler {
    551         public ThreadListQueryHandler(ContentResolver contentResolver) {
    552             super(contentResolver);
    553         }
    554 
    555         @Override
    556         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    557             switch (token) {
    558             case THREAD_LIST_QUERY_TOKEN:
    559                 mListAdapter.changeCursor(cursor);
    560                 setTitle(mTitle);
    561                 setProgressBarIndeterminateVisibility(false);
    562 
    563                 if (mNeedToMarkAsSeen) {
    564                     mNeedToMarkAsSeen = false;
    565                     Conversation.markAllConversationsAsSeen(getApplicationContext());
    566 
    567                     // Delete any obsolete threads. Obsolete threads are threads that aren't
    568                     // referenced by at least one message in the pdu or sms tables.
    569                     Conversation.asyncDeleteObsoleteThreads(mQueryHandler,
    570                             DELETE_OBSOLETE_THREADS_TOKEN);
    571                 }
    572                 break;
    573 
    574             case HAVE_LOCKED_MESSAGES_TOKEN:
    575                 long threadId = (Long)cookie;
    576                 confirmDeleteThreadDialog(new DeleteThreadListener(threadId, mQueryHandler,
    577                         ConversationList.this), threadId == -1,
    578                         cursor != null && cursor.getCount() > 0,
    579                         ConversationList.this);
    580                 break;
    581 
    582             default:
    583                 Log.e(TAG, "onQueryComplete called with unknown token " + token);
    584             }
    585         }
    586 
    587         @Override
    588         protected void onDeleteComplete(int token, Object cookie, int result) {
    589             switch (token) {
    590             case DELETE_CONVERSATION_TOKEN:
    591                 // Make sure the conversation cache reflects the threads in the DB.
    592                 Conversation.init(ConversationList.this);
    593 
    594                 // Update the notification for new messages since they
    595                 // may be deleted.
    596                 MessagingNotification.nonBlockingUpdateNewMessageIndicator(ConversationList.this,
    597                         false, false);
    598                 // Update the notification for failed messages since they
    599                 // may be deleted.
    600                 MessagingNotification.updateSendFailedNotification(ConversationList.this);
    601 
    602                 // Make sure the list reflects the delete
    603                 startAsyncQuery();
    604                 break;
    605 
    606             case DELETE_OBSOLETE_THREADS_TOKEN:
    607                 // Nothing to do here.
    608                 break;
    609             }
    610         }
    611     }
    612 
    613     private void log(String format, Object... args) {
    614         String s = String.format(format, args);
    615         Log.d(TAG, "[" + Thread.currentThread().getId() + "] " + s);
    616     }
    617 }
    618