Recycler.java revision f7e8281a223af6228e6399055a6197a1edd9bc3a
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
17package com.android.mms.util;
18
19import com.android.mms.MmsConfig;
20import com.android.mms.ui.MessageUtils;
21import com.android.mms.ui.MessagingPreferenceActivity;
22import android.database.sqlite.SqliteWrapper;
23
24import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.Context;
27import android.content.SharedPreferences;
28import android.database.Cursor;
29import android.net.Uri;
30import android.preference.PreferenceManager;
31import android.provider.BaseColumns;
32import android.provider.Telephony;
33import android.provider.Telephony.Mms;
34import android.provider.Telephony.Sms;
35import android.provider.Telephony.Sms.Conversations;
36import android.util.Log;
37
38/**
39 * The recycler is responsible for deleting old messages.
40 */
41public 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