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