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.commit(); 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.commit(); 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 return true; 449 } 450 } 451 } finally { 452 cursor.close(); 453 } 454 return false; 455 } 456 } 457 458 } 459 460 461