1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/* 2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project 3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License"); 5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License. 6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at 7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * http://www.apache.org/licenses/LICENSE-2.0 9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software 11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS, 12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and 14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License. 15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.datamodel.action; 18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.database.Cursor; 20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.net.Uri; 21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Bundle; 22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Parcel; 23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Parcelable; 24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.text.TextUtils; 25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.Factory; 27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.BugleDatabaseOperations; 28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.BugleNotifications; 29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DataModel; 30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DataModelException; 31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper; 32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseHelper.MessageColumns; 33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.DatabaseWrapper; 34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.MessagingContentProvider; 35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.sms.MmsUtils; 36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert; 37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.LogUtil; 38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.widget.WidgetConversationProvider; 39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.ArrayList; 41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.List; 42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/** 44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Action used to delete a conversation. 45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class DeleteConversationAction extends Action implements Parcelable { 47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG; 48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static void deleteConversation(final String conversationId, final long cutoffTimestamp) { 50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final DeleteConversationAction action = new DeleteConversationAction(conversationId, 51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cutoffTimestamp); 52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd action.start(); 53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final String KEY_CONVERSATION_ID = "conversation_id"; 56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final String KEY_CUTOFF_TIMESTAMP = "cutoff_timestamp"; 57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private DeleteConversationAction(final String conversationId, final long cutoffTimestamp) { 59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd super(); 60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd actionParameters.putString(KEY_CONVERSATION_ID, conversationId); 61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // TODO: Should we set cuttoff timestamp to prevent us deleting new messages? 62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd actionParameters.putLong(KEY_CUTOFF_TIMESTAMP, cutoffTimestamp); 63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Delete conversation from both the local DB and telephony in the background so sync cannot 66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // run concurrently and incorrectly try to recreate the conversation's messages locally. The 67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // telephony database can sometimes be quite slow to delete conversations, so we delete from 68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // the local DB first, notify the UI, and then delete from telephony. 69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd protected Bundle doBackgroundWork() throws DataModelException { 71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final DatabaseWrapper db = DataModel.get().getDatabase(); 72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); 74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final long cutoffTimestamp = actionParameters.getLong(KEY_CUTOFF_TIMESTAMP); 75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!TextUtils.isEmpty(conversationId)) { 77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // First find the thread id for this conversation. 78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final long threadId = BugleDatabaseOperations.getThreadId(db, conversationId); 79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (BugleDatabaseOperations.deleteConversation(db, conversationId, cutoffTimestamp)) { 81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.i(TAG, "DeleteConversationAction: Deleted local conversation " 82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + conversationId); 83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd BugleActionToasts.onConversationDeleted(); 85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Remove notifications if necessary 87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd BugleNotifications.update(true /* silent */, null /* conversationId */, 88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd BugleNotifications.UPDATE_MESSAGES); 89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // We have changed the conversation list 91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd MessagingContentProvider.notifyConversationListChanged(); 92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Notify the widget the conversation is deleted so it can go into its configure state. 94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd WidgetConversationProvider.notifyConversationDeleted( 95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Factory.get().getApplicationContext(), 96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd conversationId); 97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(TAG, "DeleteConversationAction: Could not delete local conversation " 99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + conversationId); 100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Now delete from telephony DB. MmsSmsProvider throws an exception if the thread id is 104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // less than 0. If it's greater than zero, it will delete all messages with that thread 105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // id, even if there's no corresponding row in the threads table. 106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (threadId >= 0) { 107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int count = MmsUtils.deleteThread(threadId, cutoffTimestamp); 108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (count > 0) { 109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.i(TAG, "DeleteConversationAction: Deleted telephony thread " 110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + threadId + " (cutoffTimestamp = " + cutoffTimestamp + ")"); 111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(TAG, "DeleteConversationAction: Could not delete thread from " 113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + "telephony: conversationId = " + conversationId + ", thread id = " 114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + threadId); 115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(TAG, "DeleteConversationAction: Local conversation " + conversationId 118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + " has an invalid telephony thread id; will delete messages individually"); 119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd deleteConversationMessagesFromTelephony(); 120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.e(TAG, "DeleteConversationAction: conversationId is empty"); 123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Deletes all the telephony messages for the local conversation being deleted. 130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * <p> 131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * This is a fallback used when the conversation is not associated with any telephony thread, 132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * or its thread id is invalid (e.g. negative). This is not common, but can happen sometimes 133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * (e.g. the Unknown Sender conversation). In the usual case of deleting a conversation, we 134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * don't need this because the telephony provider automatically deletes messages when a thread 135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * is deleted. 136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void deleteConversationMessagesFromTelephony() { 138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final DatabaseWrapper db = DataModel.get().getDatabase(); 139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID); 140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.notNull(conversationId); 141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final List<Uri> messageUris = new ArrayList<>(); 143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Cursor cursor = null; 144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cursor = db.query(DatabaseHelper.MESSAGES_TABLE, 146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new String[] { MessageColumns.SMS_MESSAGE_URI }, 147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd MessageColumns.CONVERSATION_ID + "=?", 148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd new String[] { conversationId }, 149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd null, null, null); 150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd while (cursor.moveToNext()) { 151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd String messageUri = cursor.getString(0); 152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd messageUris.add(Uri.parse(messageUri)); 154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (Exception e) { 155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.e(TAG, "DeleteConversationAction: Could not parse message uri " 156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + messageUri); 157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } finally { 160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (cursor != null) { 161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cursor.close(); 162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (Uri messageUri : messageUris) { 165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int count = MmsUtils.deleteMessage(messageUri); 166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (count > 0) { 167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { 168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.d(TAG, "DeleteConversationAction: Deleted telephony message " 169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + messageUri); 170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(TAG, "DeleteConversationAction: Could not delete telephony message " 173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd + messageUri); 174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd protected Object executeAction() { 180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd requestBackgroundWork(); 181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private DeleteConversationAction(final Parcel in) { 185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd super(in); 186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final Parcelable.Creator<DeleteConversationAction> CREATOR 189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd = new Parcelable.Creator<DeleteConversationAction>() { 190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public DeleteConversationAction createFromParcel(final Parcel in) { 192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new DeleteConversationAction(in); 193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public DeleteConversationAction[] newArray(final int size) { 197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new DeleteConversationAction[size]; 198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd }; 200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void writeToParcel(final Parcel parcel, final int flags) { 203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd writeActionToParcel(parcel, flags); 204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd} 206