1 /*
2  * Copyright (C) 2016 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.providers.telephony;
18 
19 import android.annotation.TargetApi;
20 import android.app.backup.FullBackupDataOutput;
21 import android.content.ContentProvider;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.ContextWrapper;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Build;
29 import android.provider.BaseColumns;
30 import android.provider.Telephony;
31 import android.test.AndroidTestCase;
32 import android.test.mock.MockContentProvider;
33 import android.test.mock.MockContentResolver;
34 import android.test.mock.MockCursor;
35 import android.util.ArrayMap;
36 import android.util.ArraySet;
37 import android.util.JsonReader;
38 import android.util.JsonWriter;
39 import android.util.SparseArray;
40 
41 import org.json.JSONArray;
42 import org.json.JSONException;
43 import org.json.JSONObject;
44 
45 import java.io.StringReader;
46 import java.io.StringWriter;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 import java.util.UUID;
55 
56 
57 /**
58  * Tests for testing backup/restore of SMS and text MMS messages.
59  * For backup it creates fake provider and checks resulting json array.
60  * For restore provides json array and checks inserts of the messages into provider.
61  */
62 @TargetApi(Build.VERSION_CODES.M)
63 public class TelephonyBackupAgentTest extends AndroidTestCase {
64     /* Map subscriptionId -> phone number */
65     private SparseArray<String> mSubId2Phone;
66     /* Map phone number -> subscriptionId */
67     private ArrayMap<String, Integer> mPhone2SubId;
68     /* Table being used for sms cursor */
69     private final List<ContentValues> mSmsTable = new ArrayList<>();
70     /* Table begin used for mms cursor */
71     private final List<ContentValues> mMmsTable = new ArrayList<>();
72     /* Table contains parts, addresses of mms */
73     private final List<ContentValues> mMmsAllContentValues = new ArrayList<>();
74     /* Cursors being used to access sms, mms tables */
75     private FakeCursor mSmsCursor, mMmsCursor;
76     /* Test data with sms and mms */
77     private ContentValues[] mSmsRows, mMmsRows;
78     /* Json representation for the test data */
79     private String[] mSmsJson, mMmsJson;
80     /* sms, mms json concatenated as json array */
81     private String mAllSmsJson, mAllMmsJson;
82 
83     private StringWriter mStringWriter;
84 
85     /* Content resolver passed to the backupAgent */
86     private MockContentResolver mMockContentResolver = new MockContentResolver();
87 
88     /* Map uri -> cursors. Being used for contentprovider. */
89     private Map<Uri, FakeCursor> mCursors;
90     /* Content provider with threadIds.*/
91     private ThreadProvider mThreadProvider = new ThreadProvider();
92 
93     private static final String EMPTY_JSON_ARRAY = "[]";
94 
95     TelephonyBackupAgent mTelephonyBackupAgent;
96 
97     @Override
setUp()98     protected void setUp() throws Exception {
99         super.setUp();
100 
101         /* Filling up subscription maps */
102         mStringWriter = new StringWriter();
103         mSubId2Phone = new SparseArray<String>();
104         mSubId2Phone.append(1, "+111111111111111");
105         mSubId2Phone.append(3, "+333333333333333");
106 
107         mPhone2SubId = new ArrayMap<>();
108         for (int i=0; i<mSubId2Phone.size(); ++i) {
109             mPhone2SubId.put(mSubId2Phone.valueAt(i), mSubId2Phone.keyAt(i));
110         }
111 
112         mCursors = new HashMap<Uri, FakeCursor>();
113         /* Bind tables to the cursors */
114         mSmsCursor = new FakeCursor(mSmsTable, TelephonyBackupAgent.SMS_PROJECTION);
115         mCursors.put(Telephony.Sms.CONTENT_URI, mSmsCursor);
116         mMmsCursor = new FakeCursor(mMmsTable, TelephonyBackupAgent.MMS_PROJECTION);
117         mCursors.put(Telephony.Mms.CONTENT_URI, mMmsCursor);
118 
119 
120         /* Generating test data */
121         mSmsRows = new ContentValues[4];
122         mSmsJson = new String[4];
123         mSmsRows[0] = createSmsRow(1, 1, "+1232132214124", "sms 1", "sms subject", 9087978987l,
124                 999999999, 3, 44, 1);
125         mSmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"address\":" +
126                 "\"+1232132214124\",\"body\":\"sms 1\",\"subject\":\"sms subject\",\"date\":" +
127                 "\"9087978987\",\"date_sent\":\"999999999\",\"status\":\"3\",\"type\":\"44\"," +
128                 "\"recipients\":[\"+123 (213) 2214124\"],\"archived\":true}";
129         mThreadProvider.setArchived(
130                 mThreadProvider.getOrCreateThreadId(new String[]{"+123 (213) 2214124"}));
131 
132         mSmsRows[1] = createSmsRow(2, 2, "+1232132214124", "sms 2", null, 9087978987l, 999999999,
133                 0, 4, 1);
134         mSmsJson[1] = "{\"address\":\"+1232132214124\",\"body\":\"sms 2\",\"date\":" +
135                 "\"9087978987\",\"date_sent\":\"999999999\",\"status\":\"0\",\"type\":\"4\"," +
136                 "\"recipients\":[\"+123 (213) 2214124\"]}";
137 
138         mSmsRows[2] = createSmsRow(4, 3, "+1232221412433 +1232221412444", "sms 3", null,
139                 111111111111l, 999999999, 2, 3, 2);
140         mSmsJson[2] =  "{\"self_phone\":\"+333333333333333\",\"address\":" +
141                 "\"+1232221412433 +1232221412444\",\"body\":\"sms 3\",\"date\":\"111111111111\"," +
142                 "\"date_sent\":" +
143                 "\"999999999\",\"status\":\"2\",\"type\":\"3\"," +
144                 "\"recipients\":[\"+1232221412433\",\"+1232221412444\"]}";
145         mThreadProvider.getOrCreateThreadId(new String[]{"+1232221412433", "+1232221412444"});
146 
147 
148         mSmsRows[3] = createSmsRow(5, 3, null, "sms 4", null,
149                 111111111111l, 999999999, 2, 3, 5);
150         mSmsJson[3] = "{\"self_phone\":\"+333333333333333\"," +
151                 "\"body\":\"sms 4\",\"date\":\"111111111111\"," +
152                 "\"date_sent\":" +
153                 "\"999999999\",\"status\":\"2\",\"type\":\"3\"}";
154 
155         mAllSmsJson = makeJsonArray(mSmsJson);
156 
157 
158 
159         mMmsRows = new ContentValues[3];
160         mMmsJson = new String[3];
161         mMmsRows[0] = createMmsRow(1 /*id*/, 1 /*subid*/, "Subject 1" /*subject*/,
162                 100 /*subcharset*/, 111111 /*date*/, 111112 /*datesent*/, 3 /*type*/,
163                 17 /*version*/, 1 /*textonly*/,
164                 11 /*msgBox*/, "location 1" /*contentLocation*/, "MMs body 1" /*body*/,
165                 111 /*body charset*/,
166                 new String[]{"+111 (111) 11111111", "+11121212", "example@example.com",
167                         "+999999999"} /*addresses*/,
168                 3 /*threadId*/);
169 
170         mMmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"sub\":\"Subject 1\"," +
171                 "\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":\"3\",\"v\":\"17\"," +
172                 "\"msg_box\":\"11\",\"ct_l\":\"location 1\"," +
173                 "\"recipients\":[\"+11121212\",\"example@example.com\",\"+999999999\"]," +
174                 "\"mms_addresses\":" +
175                 "[{\"type\":10,\"address\":\"+111 (111) 11111111\",\"charset\":100}," +
176                 "{\"type\":11,\"address\":\"+11121212\",\"charset\":101},{\"type\":12,\"address\":"+
177                 "\"example@example.com\",\"charset\":102},{\"type\":13,\"address\":\"+999999999\"" +
178                 ",\"charset\":103}],\"mms_body\":\"MMs body 1\",\"mms_charset\":111,\"" +
179                 "sub_cs\":\"100\"}";
180         mThreadProvider.getOrCreateThreadId(new String[]{"+11121212", "example@example.com",
181                 "+999999999"});
182 
183         mMmsRows[1] = createMmsRow(2 /*id*/, 2 /*subid*/, null /*subject*/, 100 /*subcharset*/,
184                 111122 /*date*/, 1111112 /*datesent*/, 4 /*type*/, 18 /*version*/, 1 /*textonly*/,
185                 222 /*msgBox*/, "location 2" /*contentLocation*/, "MMs body 2" /*body*/,
186                 121 /*body charset*/,
187                 new String[]{"+7 (333) ", "example@example.com", "+999999999"} /*addresses*/,
188                 4 /*threadId*/);
189         mMmsJson[1] = "{\"date\":\"111122\",\"date_sent\":\"1111112\",\"m_type\":\"4\"," +
190                 "\"v\":\"18\",\"msg_box\":\"222\",\"ct_l\":\"location 2\"," +
191                 "\"recipients\":[\"example@example.com\",\"+999999999\"]," +
192                 "\"mms_addresses\":" +
193                 "[{\"type\":10,\"address\":\"+7 (333) \",\"charset\":100}," +
194                 "{\"type\":11,\"address\":\"example@example.com\",\"charset\":101}," +
195                 "{\"type\":12,\"address\":\"+999999999\",\"charset\":102}]," +
196                 "\"mms_body\":\"MMs body 2\",\"mms_charset\":121}";
197         mThreadProvider.getOrCreateThreadId(new String[]{"example@example.com", "+999999999"});
198 
199         mMmsRows[2] = createMmsRow(9 /*id*/, 3 /*subid*/, "Subject 10" /*subject*/,
200                 10 /*subcharset*/, 111133 /*date*/, 1111132 /*datesent*/, 5 /*type*/,
201                 19 /*version*/, 1 /*textonly*/,
202                 333 /*msgBox*/, null /*contentLocation*/, "MMs body 3" /*body*/,
203                 131 /*body charset*/,
204                 new String[]{"333 333333333333", "+1232132214124"} /*addresses*/,
205                 1 /*threadId*/);
206 
207         mMmsJson[2] = "{\"self_phone\":\"+333333333333333\",\"sub\":\"Subject 10\"," +
208                 "\"date\":\"111133\",\"date_sent\":\"1111132\",\"m_type\":\"5\",\"v\":\"19\"," +
209                 "\"msg_box\":\"333\"," +
210                 "\"recipients\":[\"+123 (213) 2214124\"],\"archived\":true," +
211                 "\"mms_addresses\":" +
212                 "[{\"type\":10,\"address\":\"333 333333333333\",\"charset\":100}," +
213                 "{\"type\":11,\"address\":\"+1232132214124\",\"charset\":101}]," +
214                 "\"mms_body\":\"MMs body 3\",\"mms_charset\":131," +
215                 "\"sub_cs\":\"10\"}";
216         mAllMmsJson = makeJsonArray(mMmsJson);
217 
218         ContentProvider contentProvider = new MockContentProvider() {
219             @Override
220             public Cursor query(Uri uri, String[] projection, String selection,
221                                 String[] selectionArgs, String sortOrder) {
222                 if (mCursors.containsKey(uri)) {
223                     FakeCursor fakeCursor = mCursors.get(uri);
224                     if (projection != null) {
225                         fakeCursor.setProjection(projection);
226                     }
227                     fakeCursor.nextRow = 0;
228                     return fakeCursor;
229                 }
230                 fail("No cursor for " + uri.toString());
231                 return null;
232             }
233         };
234 
235         mMockContentResolver.addProvider("sms", contentProvider);
236         mMockContentResolver.addProvider("mms", contentProvider);
237         mMockContentResolver.addProvider("mms-sms", mThreadProvider);
238 
239         mTelephonyBackupAgent = new TelephonyBackupAgent();
240         mTelephonyBackupAgent.attach(new ContextWrapper(getContext()) {
241             @Override
242             public ContentResolver getContentResolver() {
243                 return mMockContentResolver;
244             }
245         });
246 
247 
248         mTelephonyBackupAgent.clearSharedPreferences();
249         mTelephonyBackupAgent.setContentResolver(mMockContentResolver);
250         mTelephonyBackupAgent.setSubId(mSubId2Phone, mPhone2SubId);
251     }
252 
253     @Override
tearDown()254     protected void tearDown() throws Exception {
255         mTelephonyBackupAgent.clearSharedPreferences();
256         super.tearDown();
257     }
258 
makeJsonArray(String[] json)259     private static String makeJsonArray(String[] json) {
260         StringBuilder stringBuilder = new StringBuilder("[");
261         for (int i=0; i<json.length; ++i) {
262             if (i > 0) {
263                 stringBuilder.append(",");
264             }
265             stringBuilder.append(json[i]);
266         }
267         stringBuilder.append("]");
268         return stringBuilder.toString();
269     }
270 
createSmsRow(int id, int subId, String address, String body, String subj, long date, long dateSent, int status, int type, long threadId)271     private static ContentValues createSmsRow(int id, int subId, String address, String body,
272                                               String subj, long date, long dateSent,
273                                               int status, int type, long threadId) {
274         ContentValues smsRow = new ContentValues();
275         smsRow.put(Telephony.Sms._ID, id);
276         smsRow.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
277         if (address != null) {
278             smsRow.put(Telephony.Sms.ADDRESS, address);
279         }
280         if (body != null) {
281             smsRow.put(Telephony.Sms.BODY, body);
282         }
283         if (subj != null) {
284             smsRow.put(Telephony.Sms.SUBJECT, subj);
285         }
286         smsRow.put(Telephony.Sms.DATE, String.valueOf(date));
287         smsRow.put(Telephony.Sms.DATE_SENT, String.valueOf(dateSent));
288         smsRow.put(Telephony.Sms.STATUS, String.valueOf(status));
289         smsRow.put(Telephony.Sms.TYPE, String.valueOf(type));
290         smsRow.put(Telephony.Sms.THREAD_ID, threadId);
291 
292         return smsRow;
293     }
294 
createMmsRow(int id, int subId, String subj, int subCharset, long date, long dateSent, int type, int version, int textOnly, int msgBox, String contentLocation, String body, int bodyCharset, String[] addresses, long threadId)295     private ContentValues createMmsRow(int id, int subId, String subj, int subCharset,
296                                        long date, long dateSent, int type, int version,
297                                        int textOnly, int msgBox,
298                                        String contentLocation, String body,
299                                        int bodyCharset, String[] addresses, long threadId) {
300         ContentValues mmsRow = new ContentValues();
301         mmsRow.put(Telephony.Mms._ID, id);
302         mmsRow.put(Telephony.Mms.SUBSCRIPTION_ID, subId);
303         if (subj != null) {
304             mmsRow.put(Telephony.Mms.SUBJECT, subj);
305             mmsRow.put(Telephony.Mms.SUBJECT_CHARSET, String.valueOf(subCharset));
306         }
307         mmsRow.put(Telephony.Mms.DATE, String.valueOf(date));
308         mmsRow.put(Telephony.Mms.DATE_SENT, String.valueOf(dateSent));
309         mmsRow.put(Telephony.Mms.MESSAGE_TYPE, String.valueOf(type));
310         mmsRow.put(Telephony.Mms.MMS_VERSION, String.valueOf(version));
311         mmsRow.put(Telephony.Mms.TEXT_ONLY, textOnly);
312         mmsRow.put(Telephony.Mms.MESSAGE_BOX, String.valueOf(msgBox));
313         if (contentLocation != null) {
314             mmsRow.put(Telephony.Mms.CONTENT_LOCATION, contentLocation);
315         }
316         mmsRow.put(Telephony.Mms.THREAD_ID, threadId);
317 
318         final Uri partUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
319                 appendPath("part").build();
320         mCursors.put(partUri, createBodyCursor(body, bodyCharset));
321         mMmsAllContentValues.add(mmsRow);
322 
323         final Uri addrUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
324                 appendPath("addr").build();
325         mCursors.put(addrUri, createAddrCursor(addresses));
326 
327         return mmsRow;
328     }
329 
330     private static final String APP_SMIL = "application/smil";
331     private static final String TEXT_PLAIN = "text/plain";
332 
333     // Cursor with parts of Mms.
createBodyCursor(String body, int charset)334     private FakeCursor createBodyCursor(String body, int charset) {
335         List<ContentValues> table = new ArrayList<>();
336         final String srcName = String.format("text.%06d.txt", 0);
337         final String smilBody = String.format(TelephonyBackupAgent.sSmilTextPart, srcName);
338         final String smil = String.format(TelephonyBackupAgent.sSmilTextOnly, smilBody);
339 
340         final ContentValues smilPart = new ContentValues();
341         smilPart.put(Telephony.Mms.Part.SEQ, -1);
342         smilPart.put(Telephony.Mms.Part.CONTENT_TYPE, APP_SMIL);
343         smilPart.put(Telephony.Mms.Part.NAME, "smil.xml");
344         smilPart.put(Telephony.Mms.Part.CONTENT_ID, "<smil>");
345         smilPart.put(Telephony.Mms.Part.CONTENT_LOCATION, "smil.xml");
346         smilPart.put(Telephony.Mms.Part.TEXT, smil);
347         mMmsAllContentValues.add(smilPart);
348 
349         final ContentValues bodyPart = new ContentValues();
350         bodyPart.put(Telephony.Mms.Part.SEQ, 0);
351         bodyPart.put(Telephony.Mms.Part.CONTENT_TYPE, TEXT_PLAIN);
352         bodyPart.put(Telephony.Mms.Part.NAME, srcName);
353         bodyPart.put(Telephony.Mms.Part.CONTENT_ID, "<"+srcName+">");
354         bodyPart.put(Telephony.Mms.Part.CONTENT_LOCATION, srcName);
355         bodyPart.put(Telephony.Mms.Part.CHARSET, charset);
356         bodyPart.put(Telephony.Mms.Part.TEXT, body);
357         table.add(bodyPart);
358         mMmsAllContentValues.add(bodyPart);
359 
360         return new FakeCursor(table, TelephonyBackupAgent.MMS_TEXT_PROJECTION);
361     }
362 
363     // Cursor with addresses of Mms.
createAddrCursor(String[] addresses)364     private FakeCursor createAddrCursor(String[] addresses) {
365         List<ContentValues> table = new ArrayList<>();
366         for (int i=0; i<addresses.length; ++i) {
367             ContentValues addr = new ContentValues();
368             addr.put(Telephony.Mms.Addr.TYPE, 10+i);
369             addr.put(Telephony.Mms.Addr.ADDRESS, addresses[i]);
370             addr.put(Telephony.Mms.Addr.CHARSET, 100+i);
371             mMmsAllContentValues.add(addr);
372             table.add(addr);
373         }
374         return new FakeCursor(table, TelephonyBackupAgent.MMS_ADDR_PROJECTION);
375     }
376 
377     /**
378      * Test with no sms in the provider.
379      * @throws Exception
380      */
testBackupSms_NoSms()381     public void testBackupSms_NoSms() throws Exception {
382         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
383         assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
384     }
385 
386     /**
387      * Test with 3 sms in the provider with the limit per file 4.
388      * @throws Exception
389      */
testBackupSms_AllSms()390     public void testBackupSms_AllSms() throws Exception {
391         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
392         mSmsTable.addAll(Arrays.asList(mSmsRows));
393         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
394         assertEquals(mAllSmsJson, mStringWriter.toString());
395     }
396 
397     /**
398      * Test with 3 sms in the provider with the limit per file 3.
399      * @throws Exception
400      */
testBackupSms_AllSmsWithExactFileLimit()401     public void testBackupSms_AllSmsWithExactFileLimit() throws Exception {
402         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
403         mSmsTable.addAll(Arrays.asList(mSmsRows));
404         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
405         assertEquals(mAllSmsJson, mStringWriter.toString());
406     }
407 
408     /**
409      * Test with 3 sms in the provider with the limit per file 1.
410      * @throws Exception
411      */
testBackupSms_AllSmsOneMessagePerFile()412     public void testBackupSms_AllSmsOneMessagePerFile() throws Exception {
413         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
414         mSmsTable.addAll(Arrays.asList(mSmsRows));
415 
416         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
417         assertEquals("[" + mSmsJson[0] + "]", mStringWriter.toString());
418 
419         mStringWriter = new StringWriter();
420         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
421         assertEquals("[" + mSmsJson[1] + "]", mStringWriter.toString());
422 
423         mStringWriter = new StringWriter();
424         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
425         assertEquals("[" + mSmsJson[2] + "]", mStringWriter.toString());
426 
427         mStringWriter = new StringWriter();
428         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
429         assertEquals("[" + mSmsJson[3] + "]", mStringWriter.toString());
430     }
431 
432     /**
433      * Test with no mms in the pvovider.
434      * @throws Exception
435      */
testBackupMms_NoMms()436     public void testBackupMms_NoMms() throws Exception {
437         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
438         assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
439     }
440 
441     /**
442      * Test with all mms.
443      * @throws Exception
444      */
testBackupMms_AllMms()445     public void testBackupMms_AllMms() throws Exception {
446         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
447         mMmsTable.addAll(Arrays.asList(mMmsRows));
448         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
449         assertEquals(mAllMmsJson, mStringWriter.toString());
450     }
451 
452     /**
453      * Test with 3 mms in the provider with the limit per file 1.
454      * @throws Exception
455      */
testBackupMms_OneMessagePerFile()456     public void testBackupMms_OneMessagePerFile() throws Exception {
457         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
458         mMmsTable.addAll(Arrays.asList(mMmsRows));
459         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
460         assertEquals("[" + mMmsJson[0] + "]", mStringWriter.toString());
461 
462         mStringWriter = new StringWriter();
463         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
464         assertEquals("[" + mMmsJson[1] + "]", mStringWriter.toString());
465 
466         mStringWriter = new StringWriter();
467         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
468         assertEquals("[" + mMmsJson[2] + "]", mStringWriter.toString());
469     }
470 
471     /**
472      * Test with 3 mms in the provider with the limit per file 3.
473      * @throws Exception
474      */
testBackupMms_WithExactFileLimit()475     public void testBackupMms_WithExactFileLimit() throws Exception {
476         mMmsTable.addAll(Arrays.asList(mMmsRows));
477         mTelephonyBackupAgent.mMaxMsgPerFile = 3;
478         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
479         assertEquals(mAllMmsJson, mStringWriter.toString());
480     }
481 
482     /**
483      * Test restore sms with the empty json array "[]".
484      * @throws Exception
485      */
testRestoreSms_NoSms()486     public void testRestoreSms_NoSms() throws Exception {
487         JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
488         FakeSmsProvider smsProvider = new FakeSmsProvider(null);
489         mMockContentResolver.addProvider("sms", smsProvider);
490         mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
491         assertEquals(0, smsProvider.getRowsAdded());
492     }
493 
494     /**
495      * Test restore sms with three sms json object in the array.
496      * @throws Exception
497      */
testRestoreSms_AllSms()498     public void testRestoreSms_AllSms() throws Exception {
499         mTelephonyBackupAgent.initUnknownSender();
500         JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
501         FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows);
502         mMockContentResolver.addProvider("sms", smsProvider);
503         mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
504         assertEquals(mSmsRows.length, smsProvider.getRowsAdded());
505         assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
506     }
507 
508     /**
509      * Test restore mms with the empty json array "[]".
510      * @throws Exception
511      */
testRestoreMms_NoMms()512     public void testRestoreMms_NoMms() throws Exception {
513         JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
514         FakeMmsProvider mmsProvider = new FakeMmsProvider(null);
515         mMockContentResolver.addProvider("mms", mmsProvider);
516         mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
517         assertEquals(0, mmsProvider.getRowsAdded());
518     }
519 
520     /**
521      * Test restore sms with three mms json object in the array.
522      * @throws Exception
523      */
testRestoreMms_AllMms()524     public void testRestoreMms_AllMms() throws Exception {
525         JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllMmsJson)));
526         FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsAllContentValues);
527         mMockContentResolver.addProvider("mms", mmsProvider);
528         mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
529         assertEquals(18, mmsProvider.getRowsAdded());
530         assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
531     }
532 
533     /**
534      * Test with quota exceeded. Checking size of the backup before it hits quota and after.
535      * It still backs up more than a quota since there is meta-info which matters with small amounts
536      * of data. The agent does not take backup meta-info into consideration.
537      * @throws Exception
538      */
testBackup_WithQuotaExceeded()539     public void testBackup_WithQuotaExceeded() throws Exception {
540         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
541         final int backupSize = 7168;
542         final int backupSizeAfterFirstQuotaHit = 6144;
543         final int backupSizeAfterSecondQuotaHit = 5120;
544 
545         mSmsTable.addAll(Arrays.asList(mSmsRows));
546         mMmsTable.addAll(Arrays.asList(mMmsRows));
547 
548         FullBackupDataOutput fullBackupDataOutput = new FullBackupDataOutput(Long.MAX_VALUE);
549         mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
550         assertEquals(backupSize, fullBackupDataOutput.getSize());
551 
552         mTelephonyBackupAgent.onQuotaExceeded(backupSize, backupSize - 100);
553         fullBackupDataOutput = new FullBackupDataOutput(Long.MAX_VALUE);
554         mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
555         assertEquals(backupSizeAfterFirstQuotaHit, fullBackupDataOutput.getSize());
556 
557         mTelephonyBackupAgent.onQuotaExceeded(backupSizeAfterFirstQuotaHit,
558                 backupSizeAfterFirstQuotaHit - 200);
559         fullBackupDataOutput = new FullBackupDataOutput(Long.MAX_VALUE);
560         mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
561         assertEquals(backupSizeAfterSecondQuotaHit, fullBackupDataOutput.getSize());
562     }
563 
564     // Adding random keys to JSON to test handling it by the BackupAgent on restore.
addRandomDataToJson(String jsonString)565     private String addRandomDataToJson(String jsonString) throws JSONException {
566         JSONArray jsonArray = new JSONArray(jsonString);
567         JSONArray res = new JSONArray();
568         for (int i = 0; i < jsonArray.length(); ++i) {
569             JSONObject jsonObject = jsonArray.getJSONObject(i);
570             jsonObject.put(UUID.randomUUID().toString(), UUID.randomUUID().toString());
571             res = res.put(jsonObject);
572         }
573         return res.toString();
574     }
575 
576     /**
577      * class for checking sms insertion into the provider on restore.
578      */
579     private class FakeSmsProvider extends MockContentProvider {
580         private int nextRow = 0;
581         private ContentValues[] mSms;
582 
FakeSmsProvider(ContentValues[] sms)583         public FakeSmsProvider(ContentValues[] sms) {
584             this.mSms = sms;
585         }
586 
587         @Override
insert(Uri uri, ContentValues values)588         public Uri insert(Uri uri, ContentValues values) {
589             assertEquals(Telephony.Sms.CONTENT_URI, uri);
590             ContentValues modifiedValues = new ContentValues(mSms[nextRow++]);
591             modifiedValues.remove(Telephony.Sms._ID);
592             modifiedValues.put(Telephony.Sms.READ, 1);
593             modifiedValues.put(Telephony.Sms.SEEN, 1);
594             if (mSubId2Phone.get(modifiedValues.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID))
595                     == null) {
596                 modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
597             }
598 
599             if (modifiedValues.get(Telephony.Sms.ADDRESS) == null) {
600                 modifiedValues.put(Telephony.Sms.ADDRESS, TelephonyBackupAgent.UNKNOWN_SENDER);
601             }
602 
603             assertEquals(modifiedValues, values);
604             return null;
605         }
606 
607         @Override
bulkInsert(Uri uri, ContentValues[] values)608         public int bulkInsert(Uri uri, ContentValues[] values) {
609             for (ContentValues cv : values) {
610                 insert(uri, cv);
611             }
612             return values.length;
613         }
614 
615         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)616         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
617                             String sortOrder) {
618             return null;
619         }
620 
getRowsAdded()621         public int getRowsAdded() {
622             return nextRow;
623         }
624     }
625 
626     /**
627      * class for checking mms insertion into the provider on restore.
628      */
629     private class FakeMmsProvider extends MockContentProvider {
630         private int nextRow = 0;
631         private List<ContentValues> mValues;
632         private long mDummyMsgId = -1;
633         private long mMsgId = -1;
634 
FakeMmsProvider(List<ContentValues> values)635         public FakeMmsProvider(List<ContentValues> values) {
636             this.mValues = values;
637         }
638 
639         @Override
insert(Uri uri, ContentValues values)640         public Uri insert(Uri uri, ContentValues values) {
641             Uri retUri = Uri.parse("dummy_uri");
642             ContentValues modifiedValues = new ContentValues(mValues.get(nextRow++));
643             if (APP_SMIL.equals(values.get(Telephony.Mms.Part.CONTENT_TYPE))) {
644                 // Smil part.
645                 assertEquals(-1, mDummyMsgId);
646                 mDummyMsgId = values.getAsLong(Telephony.Mms.Part.MSG_ID);
647             }
648 
649             if (values.get(Telephony.Mms.Part.SEQ) != null) {
650                 // Part of mms.
651                 final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
652                         .appendPath(String.valueOf(mDummyMsgId))
653                         .appendPath("part")
654                         .build();
655                 assertEquals(expectedUri, uri);
656             }
657 
658             if (values.get(Telephony.Mms.Part.MSG_ID) != null) {
659                 modifiedValues.put(Telephony.Mms.Part.MSG_ID, mDummyMsgId);
660             }
661 
662 
663             if (values.get(Telephony.Mms.SUBSCRIPTION_ID) != null) {
664                 assertEquals(Telephony.Mms.CONTENT_URI, uri);
665                 if (mSubId2Phone.get(modifiedValues.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID))
666                         == null) {
667                     modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
668                 }
669                 // Mms.
670                 modifiedValues.put(Telephony.Mms.READ, 1);
671                 modifiedValues.put(Telephony.Mms.SEEN, 1);
672                 mMsgId = modifiedValues.getAsInteger(BaseColumns._ID);
673                 retUri = Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, String.valueOf(mMsgId));
674                 modifiedValues.remove(BaseColumns._ID);
675             }
676 
677             if (values.get(Telephony.Mms.Addr.ADDRESS) != null) {
678                 // Address.
679                 final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
680                         .appendPath(String.valueOf(mMsgId))
681                         .appendPath("addr")
682                         .build();
683                 assertEquals(expectedUri, uri);
684                 assertNotSame(-1, mMsgId);
685                 modifiedValues.put(Telephony.Mms.Addr.MSG_ID, mMsgId);
686                 mDummyMsgId = -1;
687             }
688 
689             for (String key : modifiedValues.keySet()) {
690                 assertEquals("Key:"+key, modifiedValues.get(key), values.get(key));
691             }
692             assertEquals(modifiedValues.size(), values.size());
693             return retUri;
694         }
695 
696         @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)697         public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
698             final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
699                     .appendPath(String.valueOf(mDummyMsgId))
700                     .appendPath("part")
701                     .build();
702             assertEquals(expectedUri, uri);
703             ContentValues expected = new ContentValues();
704             expected.put(Telephony.Mms.Part.MSG_ID, mMsgId);
705             assertEquals(expected, values);
706             return 2;
707         }
708 
709         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)710         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
711                             String sortOrder) {
712             return null;
713         }
714 
getRowsAdded()715         public int getRowsAdded() {
716             return nextRow;
717         }
718     }
719 
720     /**
721      * class that implements MmsSms provider for thread ids.
722      */
723     private static class ThreadProvider extends MockContentProvider {
724         ArrayList<Set<Integer> > id2Thread = new ArrayList<>();
725         ArrayList<String> id2Recipient = new ArrayList<>();
726         Set<Integer> mIsThreadArchived = new HashSet<>();
727         Set<Integer> mUpdateThreadsArchived = new HashSet<>();
728 
729 
getOrCreateThreadId(final String[] recipients)730         public int getOrCreateThreadId(final String[] recipients) {
731             if (recipients == null || recipients.length == 0) {
732                 throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
733             }
734 
735             Set<Integer> ids = new ArraySet<>();
736             for (String rec : recipients) {
737                 if (!id2Recipient.contains(rec)) {
738                     id2Recipient.add(rec);
739                 }
740                 ids.add(id2Recipient.indexOf(rec)+1);
741             }
742             if (!id2Thread.contains(ids)) {
743                 id2Thread.add(ids);
744             }
745             return id2Thread.indexOf(ids)+1;
746         }
747 
setArchived(int threadId)748         public void setArchived(int threadId) {
749             mIsThreadArchived.add(threadId);
750         }
751 
getSpaceSepIds(int threadId)752         private String getSpaceSepIds(int threadId) {
753             if (id2Thread.size() < threadId) {
754                 return null;
755             }
756 
757             String spaceSepIds = null;
758             for (Integer id : id2Thread.get(threadId-1)) {
759                 spaceSepIds = (spaceSepIds == null ? "" : spaceSepIds + " ") + String.valueOf(id);
760             }
761             return spaceSepIds;
762         }
763 
getRecipient(int recipientId)764         private String getRecipient(int recipientId) {
765             return id2Recipient.get(recipientId-1);
766         }
767 
768         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)769         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
770                             String sortOrder) {
771             if (uri.equals(TelephonyBackupAgent.ALL_THREADS_URI)) {
772                 final int threadId = Integer.parseInt(selectionArgs[0]);
773                 final String spaceSepIds = getSpaceSepIds(threadId);
774                 List<ContentValues> table = new ArrayList<>();
775                 ContentValues row = new ContentValues();
776                 row.put(Telephony.Threads.RECIPIENT_IDS, spaceSepIds);
777                 table.add(row);
778                 return new FakeCursor(table, projection);
779             } else if (uri.toString().startsWith(Telephony.Threads.CONTENT_URI.toString())) {
780                 assertEquals(1, projection.length);
781                 assertEquals(Telephony.Threads.ARCHIVED, projection[0]);
782                 List<String> segments = uri.getPathSegments();
783                 final int threadId = Integer.parseInt(segments.get(segments.size() - 2));
784                 List<ContentValues> table = new ArrayList<>();
785                 ContentValues row = new ContentValues();
786                 row.put(Telephony.Threads.ARCHIVED, mIsThreadArchived.contains(threadId) ? 1 : 0);
787                 table.add(row);
788                 return new FakeCursor(table, projection);
789             } else if (uri.toString().startsWith(
790                     TelephonyBackupAgent.SINGLE_CANONICAL_ADDRESS_URI.toString())) {
791                 final int recipientId = (int)ContentUris.parseId(uri);
792                 final String recipient = getRecipient(recipientId);
793                 List<ContentValues> table = new ArrayList<>();
794                 ContentValues row = new ContentValues();
795                 row.put(Telephony.CanonicalAddressesColumns.ADDRESS, recipient);
796                 table.add(row);
797 
798                 return new FakeCursor(table,
799                         projection != null
800                                 ? projection
801                                 : new String[] { Telephony.CanonicalAddressesColumns.ADDRESS });
802             } else if (uri.toString().startsWith(
803                     TelephonyBackupAgent.THREAD_ID_CONTENT_URI.toString())) {
804                 List<String> recipients = uri.getQueryParameters("recipient");
805 
806                 final int threadId =
807                         getOrCreateThreadId(recipients.toArray(new String[recipients.size()]));
808                 List<ContentValues> table = new ArrayList<>();
809                 ContentValues row = new ContentValues();
810                 row.put(BaseColumns._ID, String.valueOf(threadId));
811                 table.add(row);
812                 return new FakeCursor(table, projection);
813             } else {
814                 fail("Unknown URI");
815             }
816             return null;
817         }
818 
819         @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)820         public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
821             assertEquals(uri, Telephony.Threads.CONTENT_URI);
822             assertEquals(values.getAsInteger(Telephony.Threads.ARCHIVED).intValue(), 1);
823             final int threadId = Integer.parseInt(selectionArgs[0]);
824             mUpdateThreadsArchived.add(threadId);
825             return 1;
826         }
827     }
828 
829     /**
830      * general cursor for serving queries.
831      */
832     private static class FakeCursor extends MockCursor {
833         String[] projection;
834         List<ContentValues> rows;
835         int nextRow = 0;
836 
FakeCursor(List<ContentValues> rows, String[] projection)837         public FakeCursor(List<ContentValues> rows, String[] projection) {
838             this.projection = projection;
839             this.rows = rows;
840         }
841 
setProjection(String[] projection)842         public void setProjection(String[] projection) {
843             this.projection = projection;
844         }
845 
846         @Override
getColumnCount()847         public int getColumnCount() {
848             return projection.length;
849         }
850 
851         @Override
getColumnName(int columnIndex)852         public String getColumnName(int columnIndex) {
853             return projection[columnIndex];
854         }
855 
856         @Override
getString(int columnIndex)857         public String getString(int columnIndex) {
858             return rows.get(nextRow).getAsString(projection[columnIndex]);
859         }
860 
861         @Override
getInt(int columnIndex)862         public int getInt(int columnIndex) {
863             return rows.get(nextRow).getAsInteger(projection[columnIndex]);
864         }
865 
866         @Override
getLong(int columnIndex)867         public long getLong(int columnIndex) {
868             return rows.get(nextRow).getAsLong(projection[columnIndex]);
869         }
870 
871         @Override
isAfterLast()872         public boolean isAfterLast() {
873             return nextRow >= getCount();
874         }
875 
876         @Override
isLast()877         public boolean isLast() {
878             return nextRow == getCount() - 1;
879         }
880 
881         @Override
moveToFirst()882         public boolean moveToFirst() {
883             nextRow = 0;
884             return getCount() > 0;
885         }
886 
887         @Override
moveToNext()888         public boolean moveToNext() {
889             return getCount() > ++nextRow;
890         }
891 
892         @Override
getCount()893         public int getCount() {
894             return rows.size();
895         }
896 
897         @Override
getColumnIndex(String columnName)898         public int getColumnIndex(String columnName) {
899             for (int i=0; i<projection.length; ++i) {
900                 if (columnName.equals(projection[i])) {
901                     return i;
902                 }
903             }
904             return -1;
905         }
906 
907         @Override
close()908         public void close() {
909         }
910     }
911 }
912