Home | History | Annotate | Download | only in action
      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 
     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 
     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
     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      */
    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
    179     protected Object executeAction() {
    180         requestBackgroundWork();
    181         return null;
    182     }
    183 
    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
    202     public void writeToParcel(final Parcel parcel, final int flags) {
    203         writeActionToParcel(parcel, flags);
    204     }
    205 }
    206