Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2009 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.mms.util;
     18 
     19 import com.android.mms.MmsConfig;
     20 import com.android.mms.ui.MessageUtils;
     21 import com.android.mms.ui.MessagingPreferenceActivity;
     22 import android.database.sqlite.SqliteWrapper;
     23 
     24 import android.content.ContentResolver;
     25 import android.content.ContentUris;
     26 import android.content.Context;
     27 import android.content.SharedPreferences;
     28 import android.database.Cursor;
     29 import android.net.Uri;
     30 import android.preference.PreferenceManager;
     31 import android.provider.BaseColumns;
     32 import android.provider.Telephony;
     33 import android.provider.Telephony.Mms;
     34 import android.provider.Telephony.Sms;
     35 import android.provider.Telephony.Sms.Conversations;
     36 import android.util.Log;
     37 
     38 /**
     39  * The recycler is responsible for deleting old messages.
     40  */
     41 public abstract class Recycler {
     42     private static final boolean LOCAL_DEBUG = false;
     43     private static final String TAG = "Recycler";
     44 
     45     // Default preference values
     46     private static final boolean DEFAULT_AUTO_DELETE  = false;
     47 
     48     private static SmsRecycler sSmsRecycler;
     49     private static MmsRecycler sMmsRecycler;
     50 
     51     public static SmsRecycler getSmsRecycler() {
     52         if (sSmsRecycler == null) {
     53             sSmsRecycler = new SmsRecycler();
     54         }
     55         return sSmsRecycler;
     56     }
     57 
     58     public static MmsRecycler getMmsRecycler() {
     59         if (sMmsRecycler == null) {
     60             sMmsRecycler = new MmsRecycler();
     61         }
     62         return sMmsRecycler;
     63     }
     64 
     65     public static boolean checkForThreadsOverLimit(Context context) {
     66         Recycler smsRecycler = getSmsRecycler();
     67         Recycler mmsRecycler = getMmsRecycler();
     68 
     69         return smsRecycler.anyThreadOverLimit(context) || mmsRecycler.anyThreadOverLimit(context);
     70     }
     71 
     72     public void deleteOldMessages(Context context) {
     73         if (LOCAL_DEBUG) {
     74             Log.v(TAG, "Recycler.deleteOldMessages this: " + this);
     75         }
     76         if (!isAutoDeleteEnabled(context)) {
     77             return;
     78         }
     79 
     80         Cursor cursor = getAllThreads(context);
     81         try {
     82             int limit = getMessageLimit(context);
     83             while (cursor.moveToNext()) {
     84                 long threadId = getThreadId(cursor);
     85                 deleteMessagesForThread(context, threadId, limit);
     86             }
     87         } finally {
     88             cursor.close();
     89         }
     90     }
     91 
     92     public void deleteOldMessagesByThreadId(Context context, long threadId) {
     93         if (LOCAL_DEBUG) {
     94             Log.v(TAG, "Recycler.deleteOldMessagesByThreadId this: " + this +
     95                     " threadId: " + threadId);
     96         }
     97         if (!isAutoDeleteEnabled(context)) {
     98             return;
     99         }
    100 
    101         deleteMessagesForThread(context, threadId, getMessageLimit(context));
    102     }
    103 
    104     public static boolean isAutoDeleteEnabled(Context context) {
    105         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    106         return prefs.getBoolean(MessagingPreferenceActivity.AUTO_DELETE,
    107                 DEFAULT_AUTO_DELETE);
    108     }
    109 
    110     abstract public int getMessageLimit(Context context);
    111 
    112     abstract public void setMessageLimit(Context context, int limit);
    113 
    114     public int getMessageMinLimit() {
    115         return MmsConfig.getMinMessageCountPerThread();
    116     }
    117 
    118     public int getMessageMaxLimit() {
    119         return MmsConfig.getMaxMessageCountPerThread();
    120     }
    121 
    122     abstract protected long getThreadId(Cursor cursor);
    123 
    124     abstract protected Cursor getAllThreads(Context context);
    125 
    126     abstract protected void deleteMessagesForThread(Context context, long threadId, int keep);
    127 
    128     abstract protected void dumpMessage(Cursor cursor, Context context);
    129 
    130     abstract protected boolean anyThreadOverLimit(Context context);
    131 
    132     public static class SmsRecycler extends Recycler {
    133         private static final String[] ALL_SMS_THREADS_PROJECTION = {
    134             Telephony.Sms.Conversations.THREAD_ID,
    135             Telephony.Sms.Conversations.MESSAGE_COUNT
    136         };
    137 
    138         private static final int ID             = 0;
    139         private static final int MESSAGE_COUNT  = 1;
    140 
    141         static private final String[] SMS_MESSAGE_PROJECTION = new String[] {
    142             BaseColumns._ID,
    143             Conversations.THREAD_ID,
    144             Sms.ADDRESS,
    145             Sms.BODY,
    146             Sms.DATE,
    147             Sms.READ,
    148             Sms.TYPE,
    149             Sms.STATUS,
    150         };
    151 
    152         // The indexes of the default columns which must be consistent
    153         // with above PROJECTION.
    154         static private final int COLUMN_ID                  = 0;
    155         static private final int COLUMN_THREAD_ID           = 1;
    156         static private final int COLUMN_SMS_ADDRESS         = 2;
    157         static private final int COLUMN_SMS_BODY            = 3;
    158         static private final int COLUMN_SMS_DATE            = 4;
    159         static private final int COLUMN_SMS_READ            = 5;
    160         static private final int COLUMN_SMS_TYPE            = 6;
    161         static private final int COLUMN_SMS_STATUS          = 7;
    162 
    163         private final String MAX_SMS_MESSAGES_PER_THREAD = "MaxSmsMessagesPerThread";
    164 
    165         public int getMessageLimit(Context context) {
    166             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    167             return prefs.getInt(MAX_SMS_MESSAGES_PER_THREAD,
    168                     MmsConfig.getDefaultSMSMessagesPerThread());
    169         }
    170 
    171         public void setMessageLimit(Context context, int limit) {
    172             SharedPreferences.Editor editPrefs =
    173                 PreferenceManager.getDefaultSharedPreferences(context).edit();
    174             editPrefs.putInt(MAX_SMS_MESSAGES_PER_THREAD, limit);
    175             editPrefs.apply();
    176         }
    177 
    178         protected long getThreadId(Cursor cursor) {
    179             return cursor.getLong(ID);
    180         }
    181 
    182         protected Cursor getAllThreads(Context context) {
    183             ContentResolver resolver = context.getContentResolver();
    184             Cursor cursor = SqliteWrapper.query(context, resolver,
    185                     Telephony.Sms.Conversations.CONTENT_URI,
    186                     ALL_SMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
    187 
    188             return cursor;
    189         }
    190 
    191         protected void deleteMessagesForThread(Context context, long threadId, int keep) {
    192             if (LOCAL_DEBUG) {
    193                 Log.v(TAG, "SMS: deleteMessagesForThread");
    194             }
    195             ContentResolver resolver = context.getContentResolver();
    196             Cursor cursor = null;
    197             try {
    198                 cursor = SqliteWrapper.query(context, resolver,
    199                         ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
    200                         SMS_MESSAGE_PROJECTION,
    201                         "locked=0",
    202                         null, "date DESC");     // get in newest to oldest order
    203                 if (cursor == null) {
    204                     Log.e(TAG, "SMS: deleteMessagesForThread got back null cursor");
    205                     return;
    206                 }
    207                 int count = cursor.getCount();
    208                 int numberToDelete = count - keep;
    209                 if (LOCAL_DEBUG) {
    210                     Log.v(TAG, "SMS: deleteMessagesForThread keep: " + keep +
    211                             " count: " + count +
    212                             " numberToDelete: " + numberToDelete);
    213                 }
    214                 if (numberToDelete <= 0) {
    215                     return;
    216                 }
    217                // Move to the keep limit and then delete everything older than that one.
    218                 cursor.move(keep);
    219                 long latestDate = cursor.getLong(COLUMN_SMS_DATE);
    220 
    221                 long cntDeleted = SqliteWrapper.delete(context, resolver,
    222                         ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
    223                         "locked=0 AND date<" + latestDate,
    224                         null);
    225                 if (LOCAL_DEBUG) {
    226                     Log.v(TAG, "SMS: deleteMessagesForThread cntDeleted: " + cntDeleted);
    227                 }
    228             } finally {
    229                 if (cursor != null) {
    230                     cursor.close();
    231                 }
    232             }
    233         }
    234 
    235         protected void dumpMessage(Cursor cursor, Context context) {
    236             long date = cursor.getLong(COLUMN_SMS_DATE);
    237             String dateStr = MessageUtils.formatTimeStampString(context, date, true);
    238             if (LOCAL_DEBUG) {
    239                 Log.v(TAG, "Recycler message " +
    240                         "\n    address: " + cursor.getString(COLUMN_SMS_ADDRESS) +
    241                         "\n    body: " + cursor.getString(COLUMN_SMS_BODY) +
    242                         "\n    date: " + dateStr +
    243                         "\n    date: " + date +
    244                         "\n    read: " + cursor.getInt(COLUMN_SMS_READ));
    245             }
    246         }
    247 
    248         @Override
    249         protected boolean anyThreadOverLimit(Context context) {
    250             Cursor cursor = getAllThreads(context);
    251             int limit = getMessageLimit(context);
    252             try {
    253                 while (cursor.moveToNext()) {
    254                     long threadId = getThreadId(cursor);
    255                     ContentResolver resolver = context.getContentResolver();
    256                     Cursor msgs = SqliteWrapper.query(context, resolver,
    257                             ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
    258                             SMS_MESSAGE_PROJECTION,
    259                             "locked=0",
    260                             null, "date DESC");     // get in newest to oldest order
    261 
    262                     if (msgs.getCount() >= limit) {
    263                         return true;
    264                     }
    265                 }
    266             } finally {
    267                 cursor.close();
    268             }
    269             return false;
    270         }
    271     }
    272 
    273     public static class MmsRecycler extends Recycler {
    274         private static final String[] ALL_MMS_THREADS_PROJECTION = {
    275             "thread_id", "count(*) as msg_count"
    276         };
    277 
    278         private static final int ID             = 0;
    279         private static final int MESSAGE_COUNT  = 1;
    280 
    281         static private final String[] MMS_MESSAGE_PROJECTION = new String[] {
    282             BaseColumns._ID,
    283             Conversations.THREAD_ID,
    284             Mms.DATE,
    285         };
    286 
    287         // The indexes of the default columns which must be consistent
    288         // with above PROJECTION.
    289         static private final int COLUMN_ID                  = 0;
    290         static private final int COLUMN_THREAD_ID           = 1;
    291         static private final int COLUMN_MMS_DATE            = 2;
    292         static private final int COLUMN_MMS_READ            = 3;
    293 
    294         private final String MAX_MMS_MESSAGES_PER_THREAD = "MaxMmsMessagesPerThread";
    295 
    296         public int getMessageLimit(Context context) {
    297             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    298             return prefs.getInt(MAX_MMS_MESSAGES_PER_THREAD,
    299                     MmsConfig.getDefaultMMSMessagesPerThread());
    300         }
    301 
    302         public void setMessageLimit(Context context, int limit) {
    303             SharedPreferences.Editor editPrefs =
    304                 PreferenceManager.getDefaultSharedPreferences(context).edit();
    305             editPrefs.putInt(MAX_MMS_MESSAGES_PER_THREAD, limit);
    306             editPrefs.apply();
    307         }
    308 
    309         protected long getThreadId(Cursor cursor) {
    310             return cursor.getLong(ID);
    311         }
    312 
    313         protected Cursor getAllThreads(Context context) {
    314             ContentResolver resolver = context.getContentResolver();
    315             Cursor cursor = SqliteWrapper.query(context, resolver,
    316                     Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, "threads"),
    317                     ALL_MMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
    318 
    319             return cursor;
    320         }
    321 
    322         public void deleteOldMessagesInSameThreadAsMessage(Context context, Uri uri) {
    323             if (LOCAL_DEBUG) {
    324                 Log.v(TAG, "MMS: deleteOldMessagesByUri");
    325             }
    326             if (!isAutoDeleteEnabled(context)) {
    327                 return;
    328             }
    329             Cursor cursor = null;
    330             long latestDate = 0;
    331             long threadId = 0;
    332             try {
    333                 String msgId = uri.getLastPathSegment();
    334                 ContentResolver resolver = context.getContentResolver();
    335                 cursor = SqliteWrapper.query(context, resolver,
    336                         Telephony.Mms.CONTENT_URI,
    337                         MMS_MESSAGE_PROJECTION,
    338                         "thread_id in (select thread_id from pdu where _id=" + msgId +
    339                             ") AND locked=0",
    340                         null, "date DESC");     // get in newest to oldest order
    341                 if (cursor == null) {
    342                     Log.e(TAG, "MMS: deleteOldMessagesInSameThreadAsMessage got back null cursor");
    343                     return;
    344                 }
    345 
    346                 int count = cursor.getCount();
    347                 int keep = getMessageLimit(context);
    348                 int numberToDelete = count - keep;
    349                 if (LOCAL_DEBUG) {
    350                     Log.v(TAG, "MMS: deleteOldMessagesByUri keep: " + keep +
    351                             " count: " + count +
    352                             " numberToDelete: " + numberToDelete);
    353                 }
    354                 if (numberToDelete <= 0) {
    355                     return;
    356                 }
    357                 // Move to the keep limit and then delete everything older than that one.
    358                 cursor.move(keep);
    359                 latestDate = cursor.getLong(COLUMN_MMS_DATE);
    360                 threadId = cursor.getLong(COLUMN_THREAD_ID);
    361             } finally {
    362                 if (cursor != null) {
    363                     cursor.close();
    364                 }
    365             }
    366             if (threadId != 0) {
    367                 deleteMessagesOlderThanDate(context, threadId, latestDate);
    368             }
    369         }
    370 
    371         protected void deleteMessagesForThread(Context context, long threadId, int keep) {
    372             if (LOCAL_DEBUG) {
    373                 Log.v(TAG, "MMS: deleteMessagesForThread");
    374             }
    375             if (threadId == 0) {
    376                 return;
    377             }
    378             Cursor cursor = null;
    379             long latestDate = 0;
    380             try {
    381                 ContentResolver resolver = context.getContentResolver();
    382                 cursor = SqliteWrapper.query(context, resolver,
    383                         Telephony.Mms.CONTENT_URI,
    384                         MMS_MESSAGE_PROJECTION,
    385                         "thread_id=" + threadId + " AND locked=0",
    386                         null, "date DESC");     // get in newest to oldest order
    387                 if (cursor == null) {
    388                     Log.e(TAG, "MMS: deleteMessagesForThread got back null cursor");
    389                     return;
    390                 }
    391 
    392                 int count = cursor.getCount();
    393                 int numberToDelete = count - keep;
    394                 if (LOCAL_DEBUG) {
    395                     Log.v(TAG, "MMS: deleteMessagesForThread keep: " + keep +
    396                             " count: " + count +
    397                             " numberToDelete: " + numberToDelete);
    398                 }
    399                 if (numberToDelete <= 0) {
    400                     return;
    401                 }
    402                 // Move to the keep limit and then delete everything older than that one.
    403                 cursor.move(keep);
    404                 latestDate = cursor.getLong(COLUMN_MMS_DATE);
    405             } finally {
    406                 if (cursor != null) {
    407                     cursor.close();
    408                 }
    409             }
    410             deleteMessagesOlderThanDate(context, threadId, latestDate);
    411         }
    412 
    413         private void deleteMessagesOlderThanDate(Context context, long threadId,
    414                 long latestDate) {
    415             long cntDeleted = SqliteWrapper.delete(context, context.getContentResolver(),
    416                     Telephony.Mms.CONTENT_URI,
    417                     "thread_id=" + threadId + " AND locked=0 AND date<" + latestDate,
    418                     null);
    419             if (LOCAL_DEBUG) {
    420                 Log.v(TAG, "MMS: deleteMessagesOlderThanDate cntDeleted: " + cntDeleted);
    421             }
    422         }
    423 
    424         protected void dumpMessage(Cursor cursor, Context context) {
    425             long id = cursor.getLong(COLUMN_ID);
    426             if (LOCAL_DEBUG) {
    427                 Log.v(TAG, "Recycler message " +
    428                         "\n    id: " + id
    429                 );
    430             }
    431         }
    432 
    433         @Override
    434         protected boolean anyThreadOverLimit(Context context) {
    435             Cursor cursor = getAllThreads(context);
    436             int limit = getMessageLimit(context);
    437             try {
    438                 while (cursor.moveToNext()) {
    439                     long threadId = getThreadId(cursor);
    440                     ContentResolver resolver = context.getContentResolver();
    441                     Cursor msgs = SqliteWrapper.query(context, resolver,
    442                             Telephony.Mms.CONTENT_URI,
    443                             MMS_MESSAGE_PROJECTION,
    444                             "thread_id=" + threadId + " AND locked=0",
    445                             null, "date DESC");     // get in newest to oldest order
    446 
    447                     if (msgs.getCount() >= limit) {
    448                         msgs.close();
    449                         return true;
    450                     }
    451                     msgs.close();
    452                 }
    453             } finally {
    454                 cursor.close();
    455             }
    456             return false;
    457         }
    458     }
    459 
    460 }
    461