1 /* 2 * Copyright (C) 2008 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; 18 19 import java.io.FileInputStream; 20 import java.util.ArrayList; 21 import java.util.Random; 22 23 import com.android.mms.data.Contact; 24 import com.android.mms.util.Recycler; 25 import android.provider.Telephony.Sms; 26 import android.provider.Telephony.Threads; 27 import android.provider.Telephony.Sms.Inbox; 28 29 import android.content.ContentResolver; 30 import android.content.ContentValues; 31 import android.content.Context; 32 import android.database.Cursor; 33 import android.database.sqlite.SQLiteDatabase; 34 import android.database.sqlite.SQLiteException; 35 import android.database.sqlite.SqliteWrapper; 36 import android.net.Uri; 37 import android.provider.Telephony.Sms.Conversations; 38 import android.test.AndroidTestCase; 39 import android.test.suitebuilder.annotation.LargeTest; 40 import android.util.Log; 41 42 /** 43 * Bang on the recycler and test it getting called simultaneously from two different threads 44 * NOTE: you first have to put the unix words file on the device: 45 * example: adb push ~/words /data/data/com.android.mms/files 46 * and then push a file that contains a comma separated list of numbers to send to. 47 * example: adb push ~/recipients /data/data/com.android.mms/files 48 * 49 */ 50 /** 51 * Bang on the recycler and test it getting called simultaneously from two different threads 52 * NOTE: you first have to put the unix words file on the device: 53 * example: adb push ~/words /data/data/com.android.mms/files 54 * and then push a file that contains a comma separated list of numbers to send to. 55 * example: adb push ~/recipients /data/data/com.android.mms/files 56 * 57 * To run just this test: 58 * runtest --test-class=com.android.mms.RecyclerTest mms 59 */ 60 public class RecyclerTest extends AndroidTestCase { 61 static final String TAG = "RecyclerTest"; 62 private ArrayList<String> mWords; 63 private ArrayList<String> mRecipients; 64 private int mWordCount; 65 private Random mRandom = new Random(); 66 private int mRecipientCnt; 67 private static final Uri sAllThreadsUri = 68 Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); 69 private static final String[] ALL_THREADS_PROJECTION = { 70 Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS, 71 Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR, 72 Threads.HAS_ATTACHMENT 73 }; 74 75 @Override setUp()76 protected void setUp() throws Exception { 77 super.setUp(); 78 Context context = getContext(); 79 80 // Read in dictionary of words 81 mWords = new ArrayList<String>(98568); // count of words in words file 82 StringBuilder sb = new StringBuilder(); 83 try { 84 Log.v(TAG, "Loading dictionary of words"); 85 FileInputStream words = context.openFileInput("words"); 86 int c; 87 while ((c = words.read()) != -1) { 88 if (c == '\r' || c == '\n') { 89 String word = sb.toString().trim(); 90 if (word.length() > 0) { 91 mWords.add(word); 92 } 93 sb.setLength(0); 94 } else { 95 sb.append((char)c); 96 } 97 } 98 words.close(); 99 mWordCount = mWords.size(); 100 Log.v(TAG, "Loaded dictionary word count: " + mWordCount); 101 } catch (Exception e) { 102 Log.e(TAG, "can't open words file at /data/data/com.android.mms/files/words"); 103 return; 104 } 105 106 // Read in list of recipients 107 mRecipients = new ArrayList<String>(); 108 try { 109 Log.v(TAG, "Loading recipients"); 110 FileInputStream recipients = context.openFileInput("recipients"); 111 int c; 112 while ((c = recipients.read()) != -1) { 113 if (c == '\r' || c == '\n' || c == ',') { 114 String recipient = sb.toString().trim(); 115 if (recipient.length() > 0) { 116 mRecipients.add(recipient); 117 } 118 sb.setLength(0); 119 } else { 120 sb.append((char)c); 121 } 122 } 123 recipients.close(); 124 Log.v(TAG, "Loaded recipients: " + mRecipients.size()); 125 } catch (Exception e) { 126 Log.e(TAG, "can't open recipients file at /data/data/com.android.mms/files/recipients"); 127 return; 128 } 129 mRecipientCnt = mRecipients.size(); 130 } 131 generateMessage()132 private String generateMessage() { 133 int wordsInMessage = mRandom.nextInt(9) + 1; // up to 10 words in the message 134 StringBuilder msg = new StringBuilder(); 135 for (int i = 0; i < wordsInMessage; i++) { 136 msg.append(mWords.get(mRandom.nextInt(mWordCount)) + " "); 137 } 138 return msg.toString(); 139 } 140 storeMessage(Context context, String address, String message)141 private Uri storeMessage(Context context, String address, String message) { 142 // Store the message in the content provider. 143 ContentValues values = new ContentValues(); 144 // values.put(Sms.ERROR_CODE, 0); 145 values.put(Inbox.ADDRESS, address); 146 147 // Use now for the timestamp to avoid confusion with clock 148 // drift between the handset and the SMSC. 149 values.put(Inbox.DATE, new Long(System.currentTimeMillis())); 150 values.put(Inbox.PROTOCOL, 0); 151 values.put(Inbox.READ, Integer.valueOf(0)); 152 // if (sms.getPseudoSubject().length() > 0) { 153 // values.put(Inbox.SUBJECT, sms.getPseudoSubject()); 154 // } 155 values.put(Inbox.REPLY_PATH_PRESENT, 0); 156 values.put(Inbox.SERVICE_CENTER, 0); 157 values.put(Inbox.BODY, message); 158 159 // Make sure we've got a thread id so after the insert we'll be able to delete 160 // excess messages. 161 Long threadId = 0L; 162 Contact cacheContact = Contact.get(address,true); 163 if (cacheContact != null) { 164 address = cacheContact.getNumber(); 165 } 166 167 if (((threadId == null) || (threadId == 0)) && (address != null)) { 168 values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId( 169 context, address)); 170 } 171 172 ContentResolver resolver = context.getContentResolver(); 173 174 Uri insertedUri = SqliteWrapper.insert(context, resolver, Inbox.CONTENT_URI, values); 175 176 // Now make sure we're not over the limit in stored messages 177 threadId = values.getAsLong(Sms.THREAD_ID); 178 Recycler.getSmsRecycler().deleteOldMessagesByThreadId(context, threadId); 179 180 return insertedUri; 181 } 182 183 Runnable mRecyclerBang = new Runnable() { 184 public void run() { 185 final int MAXSEND = Integer.MAX_VALUE; 186 187 for (int i = 0; i < MAXSEND; i++) { 188 // Put a random message to one of the random recipients in the SMS db. 189 Uri uri = storeMessage(getContext(), 190 mRecipients.get(mRandom.nextInt(mRecipientCnt)), 191 generateMessage()); 192 Log.v(TAG, "Generating msg uri: " + uri); 193 if (i > 100) { 194 // Wait until we've sent a bunch of messages to guarantee we've got 195 // some threads built up. Then check to make sure all the threads are there 196 // on each message. All these queries will provide additional stress on the 197 // sms db. 198 Cursor cursor = null; 199 try { 200 cursor = SqliteWrapper.query(getContext(), 201 getContext().getContentResolver(), sAllThreadsUri, 202 ALL_THREADS_PROJECTION, null, null, 203 Conversations.DEFAULT_SORT_ORDER); 204 assertNotNull("Cursor from thread query is null!", cursor); 205 int cnt = cursor.getCount(); 206 assertTrue("The threads appeared to have been wiped out", 207 cursor.getCount() >= mRecipientCnt); 208 } catch (SQLiteException e) { 209 Log.v(TAG, "query for threads failed with exception: " + e); 210 fail("query for threads failed with exception: " + e); 211 } finally { 212 if (cursor != null) { 213 cursor.close(); 214 } 215 } 216 } 217 } 218 } 219 }; 220 221 Runnable mSQLMemoryReleaser = new Runnable() { 222 public void run() { 223 while (true) { 224 SQLiteDatabase.releaseMemory(); 225 try { 226 Thread.sleep(5000); 227 } catch (Exception e) { 228 229 } 230 } 231 } 232 }; 233 234 /** 235 * Send a flurry of SMS and MMS messages 236 */ 237 @LargeTest testRecycler()238 public void testRecycler() throws Throwable { 239 // Start N simultaneous threads generating messages and running the recycler 240 final int THREAD_COUNT = 3; 241 ArrayList<Thread> threads = new ArrayList<Thread>(THREAD_COUNT); 242 for (int i = 0; i < THREAD_COUNT; i++) { 243 threads.add(i, new Thread(mRecyclerBang)); 244 threads.get(i).start(); 245 } 246 Thread memoryBanger = new Thread(mSQLMemoryReleaser); 247 memoryBanger.start(); 248 249 // Wait for the threads to finish 250 for (int i = 0; i < THREAD_COUNT; i++) { 251 threads.get(i).join(); 252 } 253 254 assertTrue(true); 255 } 256 } 257