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