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