Recycler.java revision 30523f5908dc28f7686bbc1363b20ea77ee29ab9
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 com.google.android.mms.util.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 Recycler sSmsRecycler;
49    private static Recycler sMmsRecycler;
50
51    public static Recycler getSmsRecycler() {
52        if (sSmsRecycler == null) {
53            sSmsRecycler = new SmsRecycler();
54        }
55        return sSmsRecycler;
56    }
57
58    public static Recycler 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        int limit = getMessageLimit(context);
82        try {
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    static class SmsRecycler extends Recycler {
133        private static final String[] ALL_SMS_THREADS_PROJECTION = {
134            Telephony.Sms.Conversations.THREAD_ID, Telephony.Sms.Conversations.MESSAGE_COUNT
135        };
136
137        private static final int ID             = 0;
138        private static final int MESSAGE_COUNT  = 1;
139
140        static private final String[] SMS_MESSAGE_PROJECTION = new String[] {
141            BaseColumns._ID,
142            Conversations.THREAD_ID,
143            Sms.ADDRESS,
144            Sms.BODY,
145            Sms.DATE,
146            Sms.READ,
147            Sms.TYPE,
148            Sms.STATUS,
149        };
150
151        // The indexes of the default columns which must be consistent
152        // with above PROJECTION.
153        static private final int COLUMN_ID                  = 0;
154        static private final int COLUMN_THREAD_ID           = 1;
155        static private final int COLUMN_SMS_ADDRESS         = 2;
156        static private final int COLUMN_SMS_BODY            = 3;
157        static private final int COLUMN_SMS_DATE            = 4;
158        static private final int COLUMN_SMS_READ            = 5;
159        static private final int COLUMN_SMS_TYPE            = 6;
160        static private final int COLUMN_SMS_STATUS          = 7;
161
162        private final String MAX_SMS_MESSAGES_PER_THREAD = "MaxSmsMessagesPerThread";
163
164        public int getMessageLimit(Context context) {
165            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
166            return prefs.getInt(MAX_SMS_MESSAGES_PER_THREAD,
167                    MmsConfig.getDefaultSMSMessagesPerThread());
168        }
169
170        public void setMessageLimit(Context context, int limit) {
171            SharedPreferences.Editor editPrefs =
172                PreferenceManager.getDefaultSharedPreferences(context).edit();
173            editPrefs.putInt(MAX_SMS_MESSAGES_PER_THREAD, limit);
174            editPrefs.commit();
175        }
176
177        protected long getThreadId(Cursor cursor) {
178            return cursor.getLong(ID);
179        }
180
181        protected Cursor getAllThreads(Context context) {
182            ContentResolver resolver = context.getContentResolver();
183            Cursor cursor = SqliteWrapper.query(context, resolver,
184                    Telephony.Sms.Conversations.CONTENT_URI,
185                    ALL_SMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
186
187            return cursor;
188        }
189
190        protected void deleteMessagesForThread(Context context, long threadId, int keep) {
191            if (LOCAL_DEBUG) {
192                Log.v(TAG, "SMS: deleteMessagesForThread");
193            }
194            ContentResolver resolver = context.getContentResolver();
195            Cursor cursor = SqliteWrapper.query(context, resolver,
196                    ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
197                    SMS_MESSAGE_PROJECTION,
198                    "locked=0",
199                    null, "date DESC");     // get in newest to oldest order
200
201            int count = cursor.getCount();
202            int numberToDelete = count - keep;
203            if (LOCAL_DEBUG) {
204                Log.v(TAG, "SMS: deleteMessagesForThread keep: " + keep +
205                        " count: " + count +
206                        " numberToDelete: " + numberToDelete);
207            }
208            if (numberToDelete <= 0) {
209                return;
210            }
211            try {
212                // Move to the keep limit and then delete everything older than that one.
213                cursor.move(keep);
214                long latestDate = cursor.getLong(COLUMN_SMS_DATE);
215
216                long cntDeleted = SqliteWrapper.delete(context, resolver,
217                        ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
218                        "locked=0 AND date<" + latestDate,
219                        null);
220                if (LOCAL_DEBUG) {
221                    Log.v(TAG, "SMS: deleteMessagesForThread cntDeleted: " + cntDeleted);
222                }
223            } finally {
224                cursor.close();
225            }
226        }
227
228        protected void dumpMessage(Cursor cursor, Context context) {
229            long date = cursor.getLong(COLUMN_SMS_DATE);
230            String dateStr = MessageUtils.formatTimeStampString(context, date, true);
231            if (LOCAL_DEBUG) {
232                Log.v(TAG, "Recycler message " +
233                        "\n    address: " + cursor.getString(COLUMN_SMS_ADDRESS) +
234                        "\n    body: " + cursor.getString(COLUMN_SMS_BODY) +
235                        "\n    date: " + dateStr +
236                        "\n    date: " + date +
237                        "\n    read: " + cursor.getInt(COLUMN_SMS_READ));
238            }
239        }
240
241        @Override
242        protected boolean anyThreadOverLimit(Context context) {
243            Cursor cursor = getAllThreads(context);
244            int limit = getMessageLimit(context);
245            try {
246                while (cursor.moveToNext()) {
247                    long threadId = getThreadId(cursor);
248                    ContentResolver resolver = context.getContentResolver();
249                    Cursor msgs = SqliteWrapper.query(context, resolver,
250                            ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId),
251                            SMS_MESSAGE_PROJECTION,
252                            "locked=0",
253                            null, "date DESC");     // get in newest to oldest order
254
255                    if (msgs.getCount() >= limit) {
256                        return true;
257                    }
258                }
259            } finally {
260                cursor.close();
261            }
262            return false;
263        }
264    }
265
266    static class MmsRecycler extends Recycler {
267        private static final String[] ALL_MMS_THREADS_PROJECTION = {
268            "thread_id", "count(*) as msg_count"
269        };
270
271        private static final int ID             = 0;
272        private static final int MESSAGE_COUNT  = 1;
273
274        static private final String[] MMS_MESSAGE_PROJECTION = new String[] {
275            BaseColumns._ID,
276            Conversations.THREAD_ID,
277            Mms.DATE,
278        };
279
280        // The indexes of the default columns which must be consistent
281        // with above PROJECTION.
282        static private final int COLUMN_ID                  = 0;
283        static private final int COLUMN_THREAD_ID           = 1;
284        static private final int COLUMN_MMS_DATE            = 2;
285        static private final int COLUMN_MMS_READ            = 3;
286
287        private final String MAX_MMS_MESSAGES_PER_THREAD = "MaxMmsMessagesPerThread";
288
289        public int getMessageLimit(Context context) {
290            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
291            return prefs.getInt(MAX_MMS_MESSAGES_PER_THREAD,
292                    MmsConfig.getDefaultMMSMessagesPerThread());
293        }
294
295        public void setMessageLimit(Context context, int limit) {
296            SharedPreferences.Editor editPrefs =
297                PreferenceManager.getDefaultSharedPreferences(context).edit();
298            editPrefs.putInt(MAX_MMS_MESSAGES_PER_THREAD, limit);
299            editPrefs.commit();
300        }
301
302        protected long getThreadId(Cursor cursor) {
303            return cursor.getLong(ID);
304        }
305
306        protected Cursor getAllThreads(Context context) {
307            ContentResolver resolver = context.getContentResolver();
308            Cursor cursor = SqliteWrapper.query(context, resolver,
309                    Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, "threads"),
310                    ALL_MMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER);
311
312            return cursor;
313        }
314
315        protected void deleteMessagesForThread(Context context, long threadId, int keep) {
316            if (LOCAL_DEBUG) {
317                Log.v(TAG, "MMS: deleteMessagesForThread");
318            }
319            ContentResolver resolver = context.getContentResolver();
320            Cursor cursor = SqliteWrapper.query(context, resolver,
321                    Telephony.Mms.CONTENT_URI,
322                    MMS_MESSAGE_PROJECTION,
323                    "thread_id=" + threadId + " AND locked=0",
324                    null, "date DESC");     // get in newest to oldest order
325
326            int count = cursor.getCount();
327            int numberToDelete = count - keep;
328            if (LOCAL_DEBUG) {
329                Log.v(TAG, "MMS: deleteMessagesForThread keep: " + keep +
330                        " count: " + count +
331                        " numberToDelete: " + numberToDelete);
332            }
333            if (numberToDelete <= 0) {
334                return;
335            }
336            try {
337                // Move to the keep limit and then delete everything older than that one.
338                cursor.move(keep);
339                long latestDate = cursor.getLong(COLUMN_MMS_DATE);
340
341                long cntDeleted = SqliteWrapper.delete(context, resolver,
342                        Telephony.Mms.CONTENT_URI,
343                        "thread_id=" + threadId + " AND locked=0 AND date<" + latestDate,
344                        null);
345                if (LOCAL_DEBUG) {
346                    Log.v(TAG, "MMS: deleteMessagesForThread cntDeleted: " + cntDeleted);
347                }
348            } finally {
349                cursor.close();
350            }
351        }
352
353        protected void dumpMessage(Cursor cursor, Context context) {
354            long id = cursor.getLong(COLUMN_ID);
355            if (LOCAL_DEBUG) {
356                Log.v(TAG, "Recycler message " +
357                        "\n    id: " + id
358                );
359            }
360        }
361
362        @Override
363        protected boolean anyThreadOverLimit(Context context) {
364            Cursor cursor = getAllThreads(context);
365            int limit = getMessageLimit(context);
366            try {
367                while (cursor.moveToNext()) {
368                    long threadId = getThreadId(cursor);
369                    ContentResolver resolver = context.getContentResolver();
370                    Cursor msgs = SqliteWrapper.query(context, resolver,
371                            Telephony.Mms.CONTENT_URI,
372                            MMS_MESSAGE_PROJECTION,
373                            "thread_id=" + threadId + " AND locked=0",
374                            null, "date DESC");     // get in newest to oldest order
375
376                    if (msgs.getCount() >= limit) {
377                        return true;
378                    }
379                }
380            } finally {
381                cursor.close();
382            }
383            return false;
384        }
385    }
386
387}
388
389
390