/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.util; import com.android.mms.MmsConfig; import com.android.mms.ui.MessageUtils; import com.android.mms.ui.MessagingPreferenceActivity; import android.database.sqlite.SqliteWrapper; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.provider.Telephony; import android.provider.Telephony.Mms; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Conversations; import android.util.Log; /** * The recycler is responsible for deleting old messages. */ public abstract class Recycler { private static final boolean LOCAL_DEBUG = false; private static final String TAG = "Recycler"; // Default preference values private static final boolean DEFAULT_AUTO_DELETE = false; private static SmsRecycler sSmsRecycler; private static MmsRecycler sMmsRecycler; public static SmsRecycler getSmsRecycler() { if (sSmsRecycler == null) { sSmsRecycler = new SmsRecycler(); } return sSmsRecycler; } public static MmsRecycler getMmsRecycler() { if (sMmsRecycler == null) { sMmsRecycler = new MmsRecycler(); } return sMmsRecycler; } public static boolean checkForThreadsOverLimit(Context context) { Recycler smsRecycler = getSmsRecycler(); Recycler mmsRecycler = getMmsRecycler(); return smsRecycler.anyThreadOverLimit(context) || mmsRecycler.anyThreadOverLimit(context); } public void deleteOldMessages(Context context) { if (LOCAL_DEBUG) { Log.v(TAG, "Recycler.deleteOldMessages this: " + this); } if (!isAutoDeleteEnabled(context)) { return; } Cursor cursor = getAllThreads(context); try { int limit = getMessageLimit(context); while (cursor.moveToNext()) { long threadId = getThreadId(cursor); deleteMessagesForThread(context, threadId, limit); } } finally { cursor.close(); } } public void deleteOldMessagesByThreadId(Context context, long threadId) { if (LOCAL_DEBUG) { Log.v(TAG, "Recycler.deleteOldMessagesByThreadId this: " + this + " threadId: " + threadId); } if (!isAutoDeleteEnabled(context)) { return; } deleteMessagesForThread(context, threadId, getMessageLimit(context)); } public static boolean isAutoDeleteEnabled(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getBoolean(MessagingPreferenceActivity.AUTO_DELETE, DEFAULT_AUTO_DELETE); } abstract public int getMessageLimit(Context context); abstract public void setMessageLimit(Context context, int limit); public int getMessageMinLimit() { return MmsConfig.getMinMessageCountPerThread(); } public int getMessageMaxLimit() { return MmsConfig.getMaxMessageCountPerThread(); } abstract protected long getThreadId(Cursor cursor); abstract protected Cursor getAllThreads(Context context); abstract protected void deleteMessagesForThread(Context context, long threadId, int keep); abstract protected void dumpMessage(Cursor cursor, Context context); abstract protected boolean anyThreadOverLimit(Context context); public static class SmsRecycler extends Recycler { private static final String[] ALL_SMS_THREADS_PROJECTION = { Telephony.Sms.Conversations.THREAD_ID, Telephony.Sms.Conversations.MESSAGE_COUNT }; private static final int ID = 0; private static final int MESSAGE_COUNT = 1; static private final String[] SMS_MESSAGE_PROJECTION = new String[] { BaseColumns._ID, Conversations.THREAD_ID, Sms.ADDRESS, Sms.BODY, Sms.DATE, Sms.READ, Sms.TYPE, Sms.STATUS, }; // The indexes of the default columns which must be consistent // with above PROJECTION. static private final int COLUMN_ID = 0; static private final int COLUMN_THREAD_ID = 1; static private final int COLUMN_SMS_ADDRESS = 2; static private final int COLUMN_SMS_BODY = 3; static private final int COLUMN_SMS_DATE = 4; static private final int COLUMN_SMS_READ = 5; static private final int COLUMN_SMS_TYPE = 6; static private final int COLUMN_SMS_STATUS = 7; private final String MAX_SMS_MESSAGES_PER_THREAD = "MaxSmsMessagesPerThread"; public int getMessageLimit(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getInt(MAX_SMS_MESSAGES_PER_THREAD, MmsConfig.getDefaultSMSMessagesPerThread()); } public void setMessageLimit(Context context, int limit) { SharedPreferences.Editor editPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); editPrefs.putInt(MAX_SMS_MESSAGES_PER_THREAD, limit); editPrefs.apply(); } protected long getThreadId(Cursor cursor) { return cursor.getLong(ID); } protected Cursor getAllThreads(Context context) { ContentResolver resolver = context.getContentResolver(); Cursor cursor = SqliteWrapper.query(context, resolver, Telephony.Sms.Conversations.CONTENT_URI, ALL_SMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER); return cursor; } protected void deleteMessagesForThread(Context context, long threadId, int keep) { if (LOCAL_DEBUG) { Log.v(TAG, "SMS: deleteMessagesForThread"); } ContentResolver resolver = context.getContentResolver(); Cursor cursor = null; try { cursor = SqliteWrapper.query(context, resolver, ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId), SMS_MESSAGE_PROJECTION, "locked=0", null, "date DESC"); // get in newest to oldest order if (cursor == null) { Log.e(TAG, "SMS: deleteMessagesForThread got back null cursor"); return; } int count = cursor.getCount(); int numberToDelete = count - keep; if (LOCAL_DEBUG) { Log.v(TAG, "SMS: deleteMessagesForThread keep: " + keep + " count: " + count + " numberToDelete: " + numberToDelete); } if (numberToDelete <= 0) { return; } // Move to the keep limit and then delete everything older than that one. cursor.move(keep); long latestDate = cursor.getLong(COLUMN_SMS_DATE); long cntDeleted = SqliteWrapper.delete(context, resolver, ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId), "locked=0 AND date<" + latestDate, null); if (LOCAL_DEBUG) { Log.v(TAG, "SMS: deleteMessagesForThread cntDeleted: " + cntDeleted); } } finally { if (cursor != null) { cursor.close(); } } } protected void dumpMessage(Cursor cursor, Context context) { long date = cursor.getLong(COLUMN_SMS_DATE); String dateStr = MessageUtils.formatTimeStampString(context, date, true); if (LOCAL_DEBUG) { Log.v(TAG, "Recycler message " + "\n address: " + cursor.getString(COLUMN_SMS_ADDRESS) + "\n body: " + cursor.getString(COLUMN_SMS_BODY) + "\n date: " + dateStr + "\n date: " + date + "\n read: " + cursor.getInt(COLUMN_SMS_READ)); } } @Override protected boolean anyThreadOverLimit(Context context) { Cursor cursor = getAllThreads(context); if (cursor == null) { return false; } int limit = getMessageLimit(context); try { while (cursor.moveToNext()) { long threadId = getThreadId(cursor); ContentResolver resolver = context.getContentResolver(); Cursor msgs = SqliteWrapper.query(context, resolver, ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId), SMS_MESSAGE_PROJECTION, "locked=0", null, "date DESC"); // get in newest to oldest order if (msgs == null) { return false; } try { if (msgs.getCount() >= limit) { return true; } } finally { msgs.close(); } } } finally { cursor.close(); } return false; } } public static class MmsRecycler extends Recycler { private static final String[] ALL_MMS_THREADS_PROJECTION = { "thread_id", "count(*) as msg_count" }; private static final int ID = 0; private static final int MESSAGE_COUNT = 1; static private final String[] MMS_MESSAGE_PROJECTION = new String[] { BaseColumns._ID, Conversations.THREAD_ID, Mms.DATE, }; // The indexes of the default columns which must be consistent // with above PROJECTION. static private final int COLUMN_ID = 0; static private final int COLUMN_THREAD_ID = 1; static private final int COLUMN_MMS_DATE = 2; static private final int COLUMN_MMS_READ = 3; private final String MAX_MMS_MESSAGES_PER_THREAD = "MaxMmsMessagesPerThread"; public int getMessageLimit(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getInt(MAX_MMS_MESSAGES_PER_THREAD, MmsConfig.getDefaultMMSMessagesPerThread()); } public void setMessageLimit(Context context, int limit) { SharedPreferences.Editor editPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); editPrefs.putInt(MAX_MMS_MESSAGES_PER_THREAD, limit); editPrefs.apply(); } protected long getThreadId(Cursor cursor) { return cursor.getLong(ID); } protected Cursor getAllThreads(Context context) { ContentResolver resolver = context.getContentResolver(); Cursor cursor = SqliteWrapper.query(context, resolver, Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, "threads"), ALL_MMS_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER); return cursor; } public void deleteOldMessagesInSameThreadAsMessage(Context context, Uri uri) { if (LOCAL_DEBUG) { Log.v(TAG, "MMS: deleteOldMessagesByUri"); } if (!isAutoDeleteEnabled(context)) { return; } Cursor cursor = null; long latestDate = 0; long threadId = 0; try { String msgId = uri.getLastPathSegment(); ContentResolver resolver = context.getContentResolver(); cursor = SqliteWrapper.query(context, resolver, Telephony.Mms.CONTENT_URI, MMS_MESSAGE_PROJECTION, "thread_id in (select thread_id from pdu where _id=" + msgId + ") AND locked=0", null, "date DESC"); // get in newest to oldest order if (cursor == null) { Log.e(TAG, "MMS: deleteOldMessagesInSameThreadAsMessage got back null cursor"); return; } int count = cursor.getCount(); int keep = getMessageLimit(context); int numberToDelete = count - keep; if (LOCAL_DEBUG) { Log.v(TAG, "MMS: deleteOldMessagesByUri keep: " + keep + " count: " + count + " numberToDelete: " + numberToDelete); } if (numberToDelete <= 0) { return; } // Move to the keep limit and then delete everything older than that one. cursor.move(keep); latestDate = cursor.getLong(COLUMN_MMS_DATE); threadId = cursor.getLong(COLUMN_THREAD_ID); } finally { if (cursor != null) { cursor.close(); } } if (threadId != 0) { deleteMessagesOlderThanDate(context, threadId, latestDate); } } protected void deleteMessagesForThread(Context context, long threadId, int keep) { if (LOCAL_DEBUG) { Log.v(TAG, "MMS: deleteMessagesForThread"); } if (threadId == 0) { return; } Cursor cursor = null; long latestDate = 0; try { ContentResolver resolver = context.getContentResolver(); cursor = SqliteWrapper.query(context, resolver, Telephony.Mms.CONTENT_URI, MMS_MESSAGE_PROJECTION, "thread_id=" + threadId + " AND locked=0", null, "date DESC"); // get in newest to oldest order if (cursor == null) { Log.e(TAG, "MMS: deleteMessagesForThread got back null cursor"); return; } int count = cursor.getCount(); int numberToDelete = count - keep; if (LOCAL_DEBUG) { Log.v(TAG, "MMS: deleteMessagesForThread keep: " + keep + " count: " + count + " numberToDelete: " + numberToDelete); } if (numberToDelete <= 0) { return; } // Move to the keep limit and then delete everything older than that one. cursor.move(keep); latestDate = cursor.getLong(COLUMN_MMS_DATE); } finally { if (cursor != null) { cursor.close(); } } deleteMessagesOlderThanDate(context, threadId, latestDate); } private void deleteMessagesOlderThanDate(Context context, long threadId, long latestDate) { long cntDeleted = SqliteWrapper.delete(context, context.getContentResolver(), Telephony.Mms.CONTENT_URI, "thread_id=" + threadId + " AND locked=0 AND date<" + latestDate, null); if (LOCAL_DEBUG) { Log.v(TAG, "MMS: deleteMessagesOlderThanDate cntDeleted: " + cntDeleted); } } protected void dumpMessage(Cursor cursor, Context context) { long id = cursor.getLong(COLUMN_ID); if (LOCAL_DEBUG) { Log.v(TAG, "Recycler message " + "\n id: " + id ); } } @Override protected boolean anyThreadOverLimit(Context context) { Cursor cursor = getAllThreads(context); if (cursor == null) { return false; } int limit = getMessageLimit(context); try { while (cursor.moveToNext()) { long threadId = getThreadId(cursor); ContentResolver resolver = context.getContentResolver(); Cursor msgs = SqliteWrapper.query(context, resolver, Telephony.Mms.CONTENT_URI, MMS_MESSAGE_PROJECTION, "thread_id=" + threadId + " AND locked=0", null, "date DESC"); // get in newest to oldest order if (msgs == null) { return false; } try { if (msgs.getCount() >= limit) { return true; } } finally { msgs.close(); } } } finally { cursor.close(); } return false; } } }