/* * Copyright (C) 2008 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; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Random; import com.android.mms.data.Contact; import com.android.mms.util.Recycler; import android.provider.Telephony.Sms; import android.provider.Telephony.Threads; import android.provider.Telephony.Sms.Inbox; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SqliteWrapper; import android.net.Uri; import android.provider.Telephony.Sms.Conversations; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; /** * Bang on the recycler and test it getting called simultaneously from two different threads * NOTE: you first have to put the unix words file on the device: * example: adb push ~/words /data/data/com.android.mms/files * and then push a file that contains a comma separated list of numbers to send to. * example: adb push ~/recipients /data/data/com.android.mms/files * */ /** * Bang on the recycler and test it getting called simultaneously from two different threads * NOTE: you first have to put the unix words file on the device: * example: adb push ~/words /data/data/com.android.mms/files * and then push a file that contains a comma separated list of numbers to send to. * example: adb push ~/recipients /data/data/com.android.mms/files * * To run just this test: * runtest --test-class=com.android.mms.RecyclerTest mms */ public class RecyclerTest extends AndroidTestCase { static final String TAG = "RecyclerTest"; private ArrayList<String> mWords; private ArrayList<String> mRecipients; private int mWordCount; private Random mRandom = new Random(); private int mRecipientCnt; private static final Uri sAllThreadsUri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); private static final String[] ALL_THREADS_PROJECTION = { Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS, Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR, Threads.HAS_ATTACHMENT }; @Override protected void setUp() throws Exception { super.setUp(); Context context = getContext(); // Read in dictionary of words mWords = new ArrayList<String>(98568); // count of words in words file StringBuilder sb = new StringBuilder(); try { Log.v(TAG, "Loading dictionary of words"); FileInputStream words = context.openFileInput("words"); int c; while ((c = words.read()) != -1) { if (c == '\r' || c == '\n') { String word = sb.toString().trim(); if (word.length() > 0) { mWords.add(word); } sb.setLength(0); } else { sb.append((char)c); } } words.close(); mWordCount = mWords.size(); Log.v(TAG, "Loaded dictionary word count: " + mWordCount); } catch (Exception e) { Log.e(TAG, "can't open words file at /data/data/com.android.mms/files/words"); return; } // Read in list of recipients mRecipients = new ArrayList<String>(); try { Log.v(TAG, "Loading recipients"); FileInputStream recipients = context.openFileInput("recipients"); int c; while ((c = recipients.read()) != -1) { if (c == '\r' || c == '\n' || c == ',') { String recipient = sb.toString().trim(); if (recipient.length() > 0) { mRecipients.add(recipient); } sb.setLength(0); } else { sb.append((char)c); } } recipients.close(); Log.v(TAG, "Loaded recipients: " + mRecipients.size()); } catch (Exception e) { Log.e(TAG, "can't open recipients file at /data/data/com.android.mms/files/recipients"); return; } mRecipientCnt = mRecipients.size(); } private String generateMessage() { int wordsInMessage = mRandom.nextInt(9) + 1; // up to 10 words in the message StringBuilder msg = new StringBuilder(); for (int i = 0; i < wordsInMessage; i++) { msg.append(mWords.get(mRandom.nextInt(mWordCount)) + " "); } return msg.toString(); } private Uri storeMessage(Context context, String address, String message) { // Store the message in the content provider. ContentValues values = new ContentValues(); // values.put(Sms.ERROR_CODE, 0); values.put(Inbox.ADDRESS, address); // Use now for the timestamp to avoid confusion with clock // drift between the handset and the SMSC. values.put(Inbox.DATE, new Long(System.currentTimeMillis())); values.put(Inbox.PROTOCOL, 0); values.put(Inbox.READ, Integer.valueOf(0)); // if (sms.getPseudoSubject().length() > 0) { // values.put(Inbox.SUBJECT, sms.getPseudoSubject()); // } values.put(Inbox.REPLY_PATH_PRESENT, 0); values.put(Inbox.SERVICE_CENTER, 0); values.put(Inbox.BODY, message); // Make sure we've got a thread id so after the insert we'll be able to delete // excess messages. Long threadId = 0L; Contact cacheContact = Contact.get(address,true); if (cacheContact != null) { address = cacheContact.getNumber(); } if (((threadId == null) || (threadId == 0)) && (address != null)) { values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId( context, address)); } ContentResolver resolver = context.getContentResolver(); Uri insertedUri = SqliteWrapper.insert(context, resolver, Inbox.CONTENT_URI, values); // Now make sure we're not over the limit in stored messages threadId = values.getAsLong(Sms.THREAD_ID); Recycler.getSmsRecycler().deleteOldMessagesByThreadId(context, threadId); return insertedUri; } Runnable mRecyclerBang = new Runnable() { public void run() { final int MAXSEND = Integer.MAX_VALUE; for (int i = 0; i < MAXSEND; i++) { // Put a random message to one of the random recipients in the SMS db. Uri uri = storeMessage(getContext(), mRecipients.get(mRandom.nextInt(mRecipientCnt)), generateMessage()); Log.v(TAG, "Generating msg uri: " + uri); if (i > 100) { // Wait until we've sent a bunch of messages to guarantee we've got // some threads built up. Then check to make sure all the threads are there // on each message. All these queries will provide additional stress on the // sms db. Cursor cursor = null; try { cursor = SqliteWrapper.query(getContext(), getContext().getContentResolver(), sAllThreadsUri, ALL_THREADS_PROJECTION, null, null, Conversations.DEFAULT_SORT_ORDER); assertNotNull("Cursor from thread query is null!", cursor); int cnt = cursor.getCount(); assertTrue("The threads appeared to have been wiped out", cursor.getCount() >= mRecipientCnt); } catch (SQLiteException e) { Log.v(TAG, "query for threads failed with exception: " + e); fail("query for threads failed with exception: " + e); } finally { if (cursor != null) { cursor.close(); } } } } } }; Runnable mSQLMemoryReleaser = new Runnable() { public void run() { while (true) { SQLiteDatabase.releaseMemory(); try { Thread.sleep(5000); } catch (Exception e) { } } } }; /** * Send a flurry of SMS and MMS messages */ @LargeTest public void testRecycler() throws Throwable { // Start N simultaneous threads generating messages and running the recycler final int THREAD_COUNT = 3; ArrayList<Thread> threads = new ArrayList<Thread>(THREAD_COUNT); for (int i = 0; i < THREAD_COUNT; i++) { threads.add(i, new Thread(mRecyclerBang)); threads.get(i).start(); } Thread memoryBanger = new Thread(mSQLMemoryReleaser); memoryBanger.start(); // Wait for the threads to finish for (int i = 0; i < THREAD_COUNT; i++) { threads.get(i).join(); } assertTrue(true); } }