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
17package com.android.mms;
18
19import java.io.FileInputStream;
20import java.util.ArrayList;
21import java.util.Random;
22
23import com.android.mms.data.Contact;
24import com.android.mms.util.Recycler;
25import android.provider.Telephony.Sms;
26import android.provider.Telephony.Threads;
27import android.provider.Telephony.Sms.Inbox;
28
29import android.content.ContentResolver;
30import android.content.ContentValues;
31import android.content.Context;
32import android.database.Cursor;
33import android.database.sqlite.SQLiteDatabase;
34import android.database.sqlite.SQLiteException;
35import android.database.sqlite.SqliteWrapper;
36import android.net.Uri;
37import android.provider.Telephony.Sms.Conversations;
38import android.test.AndroidTestCase;
39import android.test.suitebuilder.annotation.LargeTest;
40import 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 */
60public 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
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
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
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
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