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