• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
getSmsRecycler()52     public static SmsRecycler getSmsRecycler() {
53         if (sSmsRecycler == null) {
54             sSmsRecycler = new SmsRecycler();
55         }
56         return sSmsRecycler;
57     }
58 
getMmsRecycler()59     public static MmsRecycler getMmsRecycler() {
60         if (sMmsRecycler == null) {
61             sMmsRecycler = new MmsRecycler();
62         }
63         return sMmsRecycler;
64     }
65 
checkForThreadsOverLimit(Context context)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 
deleteOldMessages(Context context)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 
deleteOldMessagesByThreadId(Context context, long threadId)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 
isAutoDeleteEnabled(Context context)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 
getMessageLimit(Context context)111     abstract public int getMessageLimit(Context context);
112 
setMessageLimit(Context context, int limit)113     abstract public void setMessageLimit(Context context, int limit);
114 
getMessageMinLimit()115     public int getMessageMinLimit() {
116         return MmsConfig.getMinMessageCountPerThread();
117     }
118 
getMessageMaxLimit()119     public int getMessageMaxLimit() {
120         return MmsConfig.getMaxMessageCountPerThread();
121     }
122 
getThreadId(Cursor cursor)123     abstract protected long getThreadId(Cursor cursor);
124 
getAllThreads(Context context)125     abstract protected Cursor getAllThreads(Context context);
126 
deleteMessagesForThread(Context context, long threadId, int keep)127     abstract protected void deleteMessagesForThread(Context context, long threadId, int keep);
128 
dumpMessage(Cursor cursor, Context context)129     abstract protected void dumpMessage(Cursor cursor, Context context);
130 
anyThreadOverLimit(Context context)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 
getMessageLimit(Context context)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 
setMessageLimit(Context context, int limit)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 
getThreadId(Cursor cursor)179         protected long getThreadId(Cursor cursor) {
180             return cursor.getLong(ID);
181         }
182 
getAllThreads(Context context)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 
deleteMessagesForThread(Context context, long threadId, int keep)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 
dumpMessage(Cursor cursor, Context context)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
anyThreadOverLimit(Context context)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 
getMessageLimit(Context context)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 
setMessageLimit(Context context, int limit)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 
getThreadId(Cursor cursor)319         protected long getThreadId(Cursor cursor) {
320             return cursor.getLong(ID);
321         }
322 
getAllThreads(Context context)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 
deleteOldMessagesInSameThreadAsMessage(Context context, Uri uri)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 
deleteMessagesForThread(Context context, long threadId, int keep)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 
deleteMessagesOlderThanDate(Context context, long threadId, long latestDate)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 
dumpMessage(Cursor cursor, Context context)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
anyThreadOverLimit(Context context)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