• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)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 
initListAdapter()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      */
runOneTimeStorageLimitCheckForLegacyMessages()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.apply();
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      */
markCheckedMessageLimit()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.apply();
207     }
208 
209     @Override
onNewIntent(Intent intent)210     protected void onNewIntent(Intent intent) {
211         // Handle intents that occur after the activity has already been created.
212         startAsyncQuery();
213     }
214 
215     @Override
onStart()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
onStop()247     protected void onStop() {
248         super.onStop();
249 
250         DraftCache.getInstance().removeOnDraftChangedListener(this);
251         mListAdapter.changeCursor(null);
252     }
253 
onDraftChanged(final long threadId, final boolean hasDraft)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 
startAsyncQuery()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
onPrepareOptionsMenu(Menu menu)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
onSearchRequested()300     public boolean onSearchRequested() {
301         startSearch(null, false, null /*appData*/, false);
302         return true;
303     }
304 
305     @Override
onOptionsItemSelected(MenuItem item)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
onListItemClick(ListView l, View v, int position, long id)330     protected void onListItemClick(ListView l, View v, int position, long id) {
331         if (position == 0) {
332             createNewMessage();
333         } else {
334             // Note: don't read the thread id data from the ConversationListItem view passed in.
335             // It's unreliable to read the cached data stored in the view because the ListItem
336             // can be recycled, and the same view could be assigned to a different position
337             // if you click the list item fast enough. Instead, get the cursor at the position
338             // clicked and load the data from the cursor.
339             // (ConversationListAdapter extends CursorAdapter, so getItemAtPosition() should
340             // return the cursor object, which is moved to the position passed in)
341             Cursor cursor  = (Cursor) getListView().getItemAtPosition(position);
342             Conversation conv = Conversation.from(this, cursor);
343             long tid = conv.getThreadId();
344 
345             if (LogTag.VERBOSE) {
346                 Log.d(TAG, "onListItemClick: pos=" + position + ", view=" + v + ", tid=" + tid);
347             }
348 
349             openThread(tid);
350         }
351     }
352 
createNewMessage()353     private void createNewMessage() {
354         startActivity(ComposeMessageActivity.createIntent(this, 0));
355     }
356 
openThread(long threadId)357     private void openThread(long threadId) {
358         startActivity(ComposeMessageActivity.createIntent(this, threadId));
359     }
360 
createAddContactIntent(String address)361     public static Intent createAddContactIntent(String address) {
362         // address must be a single recipient
363         Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
364         intent.setType(Contacts.CONTENT_ITEM_TYPE);
365         if (Mms.isEmailAddress(address)) {
366             intent.putExtra(ContactsContract.Intents.Insert.EMAIL, address);
367         } else {
368             intent.putExtra(ContactsContract.Intents.Insert.PHONE, address);
369             intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE,
370                     ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
371         }
372         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
373 
374         return intent;
375     }
376 
377     private final OnCreateContextMenuListener mConvListOnCreateContextMenuListener =
378         new OnCreateContextMenuListener() {
379         public void onCreateContextMenu(ContextMenu menu, View v,
380                 ContextMenuInfo menuInfo) {
381             Cursor cursor = mListAdapter.getCursor();
382             if (cursor == null || cursor.getPosition() < 0) {
383                 return;
384             }
385             Conversation conv = Conversation.from(ConversationList.this, cursor);
386             ContactList recipients = conv.getRecipients();
387             menu.setHeaderTitle(recipients.formatNames(","));
388 
389             AdapterView.AdapterContextMenuInfo info =
390                 (AdapterView.AdapterContextMenuInfo) menuInfo;
391             if (info.position > 0) {
392                 menu.add(0, MENU_VIEW, 0, R.string.menu_view);
393 
394                 // Only show if there's a single recipient
395                 if (recipients.size() == 1) {
396                     // do we have this recipient in contacts?
397                     if (recipients.get(0).existsInDatabase()) {
398                         menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact);
399                     } else {
400                         menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts);
401                     }
402                 }
403                 menu.add(0, MENU_DELETE, 0, R.string.menu_delete);
404             }
405         }
406     };
407 
408     @Override
onContextItemSelected(MenuItem item)409     public boolean onContextItemSelected(MenuItem item) {
410         Cursor cursor = mListAdapter.getCursor();
411         if (cursor != null && cursor.getPosition() >= 0) {
412             Conversation conv = Conversation.from(ConversationList.this, cursor);
413             long threadId = conv.getThreadId();
414             switch (item.getItemId()) {
415             case MENU_DELETE: {
416                 confirmDeleteThread(threadId, mQueryHandler);
417                 break;
418             }
419             case MENU_VIEW: {
420                 openThread(threadId);
421                 break;
422             }
423             case MENU_VIEW_CONTACT: {
424                 Contact contact = conv.getRecipients().get(0);
425                 Intent intent = new Intent(Intent.ACTION_VIEW, contact.getUri());
426                 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
427                 startActivity(intent);
428                 break;
429             }
430             case MENU_ADD_TO_CONTACTS: {
431                 String address = conv.getRecipients().get(0).getNumber();
432                 startActivity(createAddContactIntent(address));
433                 break;
434             }
435             default:
436                 break;
437             }
438         }
439         return super.onContextItemSelected(item);
440     }
441 
442     @Override
onConfigurationChanged(Configuration newConfig)443     public void onConfigurationChanged(Configuration newConfig) {
444         // We override this method to avoid restarting the entire
445         // activity when the keyboard is opened (declared in
446         // AndroidManifest.xml).  Because the only translatable text
447         // in this activity is "New Message", which has the full width
448         // of phone to work with, localization shouldn't be a problem:
449         // no abbreviated alternate words should be needed even in
450         // 'wide' languages like German or Russian.
451 
452         super.onConfigurationChanged(newConfig);
453         if (DEBUG) Log.v(TAG, "onConfigurationChanged: " + newConfig);
454     }
455 
456     /**
457      * Start the process of putting up a dialog to confirm deleting a thread,
458      * but first start a background query to see if any of the threads or thread
459      * contain locked messages so we'll know how detailed of a UI to display.
460      * @param threadId id of the thread to delete or -1 for all threads
461      * @param handler query handler to do the background locked query
462      */
confirmDeleteThread(long threadId, AsyncQueryHandler handler)463     public static void confirmDeleteThread(long threadId, AsyncQueryHandler handler) {
464         Conversation.startQueryHaveLockedMessages(handler, threadId,
465                 HAVE_LOCKED_MESSAGES_TOKEN);
466     }
467 
468     /**
469      * Build and show the proper delete thread dialog. The UI is slightly different
470      * depending on whether there are locked messages in the thread(s) and whether we're
471      * deleting a single thread or all threads.
472      * @param listener gets called when the delete button is pressed
473      * @param deleteAll whether to show a single thread or all threads UI
474      * @param hasLockedMessages whether the thread(s) contain locked messages
475      * @param context used to load the various UI elements
476      */
confirmDeleteThreadDialog(final DeleteThreadListener listener, boolean deleteAll, boolean hasLockedMessages, Context context)477     public static void confirmDeleteThreadDialog(final DeleteThreadListener listener,
478             boolean deleteAll,
479             boolean hasLockedMessages,
480             Context context) {
481         View contents = View.inflate(context, R.layout.delete_thread_dialog_view, null);
482         TextView msg = (TextView)contents.findViewById(R.id.message);
483         msg.setText(deleteAll
484                 ? R.string.confirm_delete_all_conversations
485                         : R.string.confirm_delete_conversation);
486         final CheckBox checkbox = (CheckBox)contents.findViewById(R.id.delete_locked);
487         if (!hasLockedMessages) {
488             checkbox.setVisibility(View.GONE);
489         } else {
490             listener.setDeleteLockedMessage(checkbox.isChecked());
491             checkbox.setOnClickListener(new View.OnClickListener() {
492                 public void onClick(View v) {
493                     listener.setDeleteLockedMessage(checkbox.isChecked());
494                 }
495             });
496         }
497 
498         AlertDialog.Builder builder = new AlertDialog.Builder(context);
499         builder.setTitle(R.string.confirm_dialog_title)
500             .setIcon(android.R.drawable.ic_dialog_alert)
501         .setCancelable(true)
502         .setPositiveButton(R.string.delete, listener)
503         .setNegativeButton(R.string.no, null)
504         .setView(contents)
505         .show();
506     }
507 
508     private final OnKeyListener mThreadListKeyListener = new OnKeyListener() {
509         public boolean onKey(View v, int keyCode, KeyEvent event) {
510             if (event.getAction() == KeyEvent.ACTION_DOWN) {
511                 switch (keyCode) {
512                     case KeyEvent.KEYCODE_DEL: {
513                         long id = getListView().getSelectedItemId();
514                         if (id > 0) {
515                             confirmDeleteThread(id, mQueryHandler);
516                         }
517                         return true;
518                     }
519                 }
520             }
521             return false;
522         }
523     };
524 
525     public static class DeleteThreadListener implements OnClickListener {
526         private final long mThreadId;
527         private final AsyncQueryHandler mHandler;
528         private final Context mContext;
529         private boolean mDeleteLockedMessages;
530 
DeleteThreadListener(long threadId, AsyncQueryHandler handler, Context context)531         public DeleteThreadListener(long threadId, AsyncQueryHandler handler, Context context) {
532             mThreadId = threadId;
533             mHandler = handler;
534             mContext = context;
535         }
536 
setDeleteLockedMessage(boolean deleteLockedMessages)537         public void setDeleteLockedMessage(boolean deleteLockedMessages) {
538             mDeleteLockedMessages = deleteLockedMessages;
539         }
540 
onClick(DialogInterface dialog, final int whichButton)541         public void onClick(DialogInterface dialog, final int whichButton) {
542             MessageUtils.handleReadReport(mContext, mThreadId,
543                     PduHeaders.READ_STATUS__DELETED_WITHOUT_BEING_READ, new Runnable() {
544                 public void run() {
545                     int token = DELETE_CONVERSATION_TOKEN;
546                     if (mThreadId == -1) {
547                         Conversation.startDeleteAll(mHandler, token, mDeleteLockedMessages);
548                         DraftCache.getInstance().refresh();
549                     } else {
550                         Conversation.startDelete(mHandler, token, mDeleteLockedMessages,
551                                 mThreadId);
552                         DraftCache.getInstance().setDraftState(mThreadId, false);
553                     }
554                 }
555             });
556             dialog.dismiss();
557         }
558     }
559 
560     private final class ThreadListQueryHandler extends AsyncQueryHandler {
ThreadListQueryHandler(ContentResolver contentResolver)561         public ThreadListQueryHandler(ContentResolver contentResolver) {
562             super(contentResolver);
563         }
564 
565         @Override
onQueryComplete(int token, Object cookie, Cursor cursor)566         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
567             switch (token) {
568             case THREAD_LIST_QUERY_TOKEN:
569                 mListAdapter.changeCursor(cursor);
570                 setTitle(mTitle);
571                 setProgressBarIndeterminateVisibility(false);
572 
573                 if (mNeedToMarkAsSeen) {
574                     mNeedToMarkAsSeen = false;
575                     Conversation.markAllConversationsAsSeen(getApplicationContext());
576 
577                     // Delete any obsolete threads. Obsolete threads are threads that aren't
578                     // referenced by at least one message in the pdu or sms tables.
579                     Conversation.asyncDeleteObsoleteThreads(mQueryHandler,
580                             DELETE_OBSOLETE_THREADS_TOKEN);
581                 }
582                 break;
583 
584             case HAVE_LOCKED_MESSAGES_TOKEN:
585                 long threadId = (Long)cookie;
586                 confirmDeleteThreadDialog(new DeleteThreadListener(threadId, mQueryHandler,
587                         ConversationList.this), threadId == -1,
588                         cursor != null && cursor.getCount() > 0,
589                         ConversationList.this);
590                 break;
591 
592             default:
593                 Log.e(TAG, "onQueryComplete called with unknown token " + token);
594             }
595         }
596 
597         @Override
onDeleteComplete(int token, Object cookie, int result)598         protected void onDeleteComplete(int token, Object cookie, int result) {
599             switch (token) {
600             case DELETE_CONVERSATION_TOKEN:
601                 // Make sure the conversation cache reflects the threads in the DB.
602                 Conversation.init(ConversationList.this);
603 
604                 // Update the notification for new messages since they
605                 // may be deleted.
606                 MessagingNotification.nonBlockingUpdateNewMessageIndicator(ConversationList.this,
607                         false, false);
608                 // Update the notification for failed messages since they
609                 // may be deleted.
610                 MessagingNotification.updateSendFailedNotification(ConversationList.this);
611 
612                 // Make sure the list reflects the delete
613                 startAsyncQuery();
614                 break;
615 
616             case DELETE_OBSOLETE_THREADS_TOKEN:
617                 // Nothing to do here.
618                 break;
619             }
620         }
621     }
622 
log(String format, Object... args)623     private void log(String format, Object... args) {
624         String s = String.format(format, args);
625         Log.d(TAG, "[" + Thread.currentThread().getId() + "] " + s);
626     }
627 }
628