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 android.content.ContentResolver;
     20 import android.content.ContentUris;
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.database.Cursor;
     24 import android.database.sqlite.SqliteWrapper;
     25 import android.net.Uri;
     26 import android.preference.PreferenceManager;
     27 import android.provider.BaseColumns;
     28 import android.provider.Telephony;
     29 import android.provider.Telephony.Mms;
     30 import android.provider.Telephony.Sms;
     31 import android.provider.Telephony.Sms.Conversations;
     32 import android.util.Log;
     33 
     34 import com.android.mms.MmsConfig;
     35 import com.android.mms.ui.MessageUtils;
     36 import com.android.mms.ui.MessagingPreferenceActivity;
     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             if (cursor == null) {
    252                 return false;
    253             }
    254             int limit = getMessageLimit(context);
    255             try {
    256                 while (cursor.moveToNext()) {
    257                     long threadId = getThreadId(cursor);
    258                     ContentResolver resolver = context.getContentResolver();
    259                     Cursor msgs = SqliteWrapper.query(context, resolver,
    260                             ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
    261                             SMS_MESSAGE_PROJECTION,
    262                             "locked=0",
    263                             null, "date DESC");     // get in newest to oldest order
    264                     if (msgs == null) {
    265                         return false;
    266                     }
    267                     try {
    268                         if (msgs.getCount() >= limit) {
    269                             return true;
    270                         }
    271                     } finally {
    272                         msgs.close();
    273                     }
    274                 }
    275             } finally {
    276                 cursor.close();
    277             }
    278             return false;
    279         }
    280     }
    281 
    282     public static class MmsRecycler extends Recycler {
    283         private static final String[] ALL_MMS_THREADS_PROJECTION = {
    284             "thread_id", "count(*) as msg_count"
    285         };
    286 
    287         private static final int ID             = 0;
    288         private static final int MESSAGE_COUNT  = 1;
    289 
    290         static private final String[] MMS_MESSAGE_PROJECTION = new String[] {
    291             BaseColumns._ID,
    292             Conversations.THREAD_ID,
    293             Mms.DATE,
    294         };
    295 
    296         // The indexes of the default columns which must be consistent
    297         // with above PROJECTION.
    298         static private final int COLUMN_ID                  = 0;
    299         static private final int COLUMN_THREAD_ID           = 1;
    300         static private final int COLUMN_MMS_DATE            = 2;
    301         static private final int COLUMN_MMS_READ            = 3;
    302 
    303         private final String MAX_MMS_MESSAGES_PER_THREAD = "MaxMmsMessagesPerThread";
    304 
    305         public int getMessageLimit(Context context) {
    306             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    307             return prefs.getInt(MAX_MMS_MESSAGES_PER_THREAD,
    308                     MmsConfig.getDefaultMMSMessagesPerThread());
    309         }
    310 
    311         public void setMessageLimit(Context context, int limit) {
    312             SharedPreferences.Editor editPrefs =
    313                 PreferenceManager.getDefaultSharedPreferences(context).edit();
    314             editPrefs.putInt(MAX_MMS_MESSAGES_PER_THREAD, limit);
    315             editPrefs.apply();
    316         }
    317 
    318         protected long getThreadId(Cursor cursor) {
    319             return cursor.getLong(ID);
    320         }
    321 
    322         protected Cursor getAllThreads(Context context) {
    323             ContentResolver resolver = context.getContentResolver();
    324             Cursor cursor = SqliteWrapper.query(context, resolver,
    325                     Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, "threads"),
    326                     ALL_MMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
    327 
    328             return cursor;
    329         }
    330 
    331         public void deleteOldMessagesInSameThreadAsMessage(Context context, Uri uri) {
    332             if (LOCAL_DEBUG) {
    333                 Log.v(TAG, "MMS: deleteOldMessagesByUri");
    334             }
    335             if (!isAutoDeleteEnabled(context)) {
    336                 return;
    337             }
    338             Cursor cursor = null;
    339             long latestDate = 0;
    340             long threadId = 0;
    341             try {
    342                 String msgId = uri.getLastPathSegment();
    343                 ContentResolver resolver = context.getContentResolver();
    344                 cursor = SqliteWrapper.query(context, resolver,
    345                         Telephony.Mms.CONTENT_URI,
    346                         MMS_MESSAGE_PROJECTION,
    347                         "thread_id in (select thread_id from pdu where _id=" + msgId +
    348                             ") AND locked=0",
    349                         null, "date DESC");     // get in newest to oldest order
    350                 if (cursor == null) {
    351                     Log.e(TAG, "MMS: deleteOldMessagesInSameThreadAsMessage got back null cursor");
    352                     return;
    353                 }
    354 
    355                 int count = cursor.getCount();
    356                 int keep = getMessageLimit(context);
    357                 int numberToDelete = count - keep;
    358                 if (LOCAL_DEBUG) {
    359                     Log.v(TAG, "MMS: deleteOldMessagesByUri keep: " + keep +
    360                             " count: " + count +
    361                             " numberToDelete: " + numberToDelete);
    362                 }
    363                 if (numberToDelete <= 0) {
    364                     return;
    365                 }
    366                 // Move to the keep limit and then delete everything older than that one.
    367                 cursor.move(keep);
    368                 latestDate = cursor.getLong(COLUMN_MMS_DATE);
    369                 threadId = cursor.getLong(COLUMN_THREAD_ID);
    370             } finally {
    371                 if (cursor != null) {
    372                     cursor.close();
    373                 }
    374             }
    375             if (threadId != 0) {
    376                 deleteMessagesOlderThanDate(context, threadId, latestDate);
    377             }
    378         }
    379 
    380         protected void deleteMessagesForThread(Context context, long threadId, int keep) {
    381             if (LOCAL_DEBUG) {
    382                 Log.v(TAG, "MMS: deleteMessagesForThread");
    383             }
    384             if (threadId == 0) {
    385                 return;
    386             }
    387             Cursor cursor = null;
    388             long latestDate = 0;
    389             try {
    390                 ContentResolver resolver = context.getContentResolver();
    391                 cursor = SqliteWrapper.query(context, resolver,
    392                         Telephony.Mms.CONTENT_URI,
    393                         MMS_MESSAGE_PROJECTION,
    394                         "thread_id=" + threadId + " AND locked=0",
    395                         null, "date DESC");     // get in newest to oldest order
    396                 if (cursor == null) {
    397                     Log.e(TAG, "MMS: deleteMessagesForThread got back null cursor");
    398                     return;
    399                 }
    400 
    401                 int count = cursor.getCount();
    402                 int numberToDelete = count - keep;
    403                 if (LOCAL_DEBUG) {
    404                     Log.v(TAG, "MMS: deleteMessagesForThread keep: " + keep +
    405                             " count: " + count +
    406                             " numberToDelete: " + numberToDelete);
    407                 }
    408                 if (numberToDelete <= 0) {
    409                     return;
    410                 }
    411                 // Move to the keep limit and then delete everything older than that one.
    412                 cursor.move(keep);
    413                 latestDate = cursor.getLong(COLUMN_MMS_DATE);
    414             } finally {
    415                 if (cursor != null) {
    416                     cursor.close();
    417                 }
    418             }
    419             deleteMessagesOlderThanDate(context, threadId, latestDate);
    420         }
    421 
    422         private void deleteMessagesOlderThanDate(Context context, long threadId,
    423                 long latestDate) {
    424             long cntDeleted = SqliteWrapper.delete(context, context.getContentResolver(),
    425                     Telephony.Mms.CONTENT_URI,
    426                     "thread_id=" + threadId + " AND locked=0 AND date<" + latestDate,
    427                     null);
    428             if (LOCAL_DEBUG) {
    429                 Log.v(TAG, "MMS: deleteMessagesOlderThanDate cntDeleted: " + cntDeleted);
    430             }
    431         }
    432 
    433         protected void dumpMessage(Cursor cursor, Context context) {
    434             long id = cursor.getLong(COLUMN_ID);
    435             if (LOCAL_DEBUG) {
    436                 Log.v(TAG, "Recycler message " +
    437                         "\n    id: " + id
    438                 );
    439             }
    440         }
    441 
    442         @Override
    443         protected boolean anyThreadOverLimit(Context context) {
    444             Cursor cursor = getAllThreads(context);
    445             if (cursor == null) {
    446                 return false;
    447             }
    448             int limit = getMessageLimit(context);
    449             try {
    450                 while (cursor.moveToNext()) {
    451                     long threadId = getThreadId(cursor);
    452                     ContentResolver resolver = context.getContentResolver();
    453                     Cursor msgs = SqliteWrapper.query(context, resolver,
    454                             Telephony.Mms.CONTENT_URI,
    455                             MMS_MESSAGE_PROJECTION,
    456                             "thread_id=" + threadId + " AND locked=0",
    457                             null, "date DESC");     // get in newest to oldest order
    458 
    459                     if (msgs == null) {
    460                         return false;
    461                     }
    462                     try {
    463                         if (msgs.getCount() >= limit) {
    464                             return true;
    465                         }
    466                     } finally {
    467                         msgs.close();
    468                     }
    469                 }
    470             } finally {
    471                 cursor.close();
    472             }
    473             return false;
    474         }
    475     }
    476 
    477 }
    478