• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static org.junit.Assert.assertArrayEquals;
20 
21 import android.annotation.TargetApi;
22 import android.app.backup.BackupDataOutput;
23 import android.app.backup.FullBackupDataOutput;
24 import android.content.ContentProvider;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.ContentValues;
28 import android.content.ContextWrapper;
29 import android.database.Cursor;
30 import android.net.Uri;
31 import android.os.Build;
32 import android.os.ParcelFileDescriptor;
33 import android.provider.BaseColumns;
34 import android.provider.Telephony;
35 import android.test.AndroidTestCase;
36 import android.test.mock.MockContentProvider;
37 import android.test.mock.MockContentResolver;
38 import android.test.mock.MockCursor;
39 import android.text.TextUtils;
40 import android.util.ArrayMap;
41 import android.util.ArraySet;
42 import android.util.JsonReader;
43 import android.util.JsonWriter;
44 import android.util.SparseArray;
45 
46 import com.android.internal.telephony.PhoneFactory;
47 
48 import libcore.io.IoUtils;
49 
50 import com.google.android.mms.pdu.CharacterSets;
51 
52 import org.json.JSONArray;
53 import org.json.JSONException;
54 import org.json.JSONObject;
55 
56 import java.io.File;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.StringReader;
60 import java.io.StringWriter;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collections;
64 import java.util.HashMap;
65 import java.util.HashSet;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Set;
69 import java.util.UUID;
70 import java.util.concurrent.TimeUnit;
71 
72 /**
73  * Tests for testing backup/restore of SMS and text MMS messages.
74  * For backup it creates fake provider and checks resulting json array.
75  * For restore provides json array and checks inserts of the messages into provider.
76  *
77  * To run this test from the android root: runtest --path packages/providers/TelephonyProvider/
78  */
79 @TargetApi(Build.VERSION_CODES.O)
80 public class TelephonyBackupAgentTest extends AndroidTestCase {
81     /* Map subscriptionId -> phone number */
82     private SparseArray<String> mSubId2Phone;
83     /* Map phone number -> subscriptionId */
84     private ArrayMap<String, Integer> mPhone2SubId;
85     /* Table being used for sms cursor */
86     private final List<ContentValues> mSmsTable = new ArrayList<>();
87     /* Table begin used for mms cursor */
88     private final List<ContentValues> mMmsTable = new ArrayList<>();
89     /* Table contains parts, addresses of mms */
90     private final List<ContentValues> mMmsAllContentValues = new ArrayList<>();
91     /* Table contains parts, addresses of mms for null body test case */
92     private final List<ContentValues> mMmsNullBodyContentValues = new ArrayList<>();
93     /* Cursors being used to access sms, mms tables */
94     private FakeCursor mSmsCursor, mMmsCursor;
95     /* Test data with sms and mms */
96     private ContentValues[] mSmsRows, mMmsRows, mMmsAttachmentRows;
97     /* Json representation for the test data */
98     private String[] mSmsJson, mMmsJson, mMmsAttachmentJson;
99     /* sms, mms json concatenated as json array */
100     private String mAllSmsJson, mAllMmsJson, mMmsAllAttachmentJson, mMmsAllNullBodyJson;
101 
102     private StringWriter mStringWriter;
103 
104     /* Content resolver passed to the backupAgent */
105     private MockContentResolver mMockContentResolver = new MockContentResolver();
106 
107     /* Map uri -> cursors. Being used for contentprovider. */
108     private Map<Uri, FakeCursor> mCursors;
109     /* Content provider with threadIds.*/
110     private ThreadProvider mThreadProvider = new ThreadProvider();
111 
112     private static final String EMPTY_JSON_ARRAY = "[]";
113 
114     TelephonyBackupAgent mTelephonyBackupAgent;
115 
116     @Override
setUp()117     protected void setUp() throws Exception {
118         super.setUp();
119 
120         /* Filling up subscription maps */
121         mStringWriter = new StringWriter();
122         mSubId2Phone = new SparseArray<String>();
123         mSubId2Phone.append(1, "+111111111111111");
124         mSubId2Phone.append(3, "+333333333333333");
125 
126         mPhone2SubId = new ArrayMap<>();
127         for (int i=0; i<mSubId2Phone.size(); ++i) {
128             mPhone2SubId.put(mSubId2Phone.valueAt(i), mSubId2Phone.keyAt(i));
129         }
130 
131         mCursors = new HashMap<Uri, FakeCursor>();
132         /* Bind tables to the cursors */
133         mSmsCursor = new FakeCursor(mSmsTable, TelephonyBackupAgent.SMS_PROJECTION);
134         mCursors.put(Telephony.Sms.CONTENT_URI, mSmsCursor);
135         mMmsCursor = new FakeCursor(mMmsTable, TelephonyBackupAgent.MMS_PROJECTION);
136         mCursors.put(Telephony.Mms.CONTENT_URI, mMmsCursor);
137 
138 
139         /* Generating test data */
140         mSmsRows = new ContentValues[4];
141         mSmsJson = new String[4];
142         mSmsRows[0] = createSmsRow(1, 1, "+1232132214124", "sms 1", "sms subject", 9087978987l,
143                 999999999, 3, 44, 1, false);
144         mSmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"address\":" +
145                 "\"+1232132214124\",\"body\":\"sms 1\",\"subject\":\"sms subject\",\"date\":" +
146                 "\"9087978987\",\"date_sent\":\"999999999\",\"status\":\"3\",\"type\":\"44\"," +
147                 "\"recipients\":[\"+123 (213) 2214124\"],\"archived\":true,\"read\":\"0\"}";
148         mThreadProvider.setArchived(
149                 mThreadProvider.getOrCreateThreadId(new String[]{"+123 (213) 2214124"}));
150 
151         mSmsRows[1] = createSmsRow(2, 2, "+1232132214124", "sms 2", null, 9087978987l, 999999999,
152                 0, 4, 1, true);
153         mSmsJson[1] = "{\"address\":\"+1232132214124\",\"body\":\"sms 2\",\"date\":" +
154                 "\"9087978987\",\"date_sent\":\"999999999\",\"status\":\"0\",\"type\":\"4\"," +
155                 "\"recipients\":[\"+123 (213) 2214124\"],\"read\":\"1\"}";
156 
157         mSmsRows[2] = createSmsRow(4, 3, "+1232221412433 +1232221412444", "sms 3", null,
158                 111111111111l, 999999999, 2, 3, 2, false);
159         mSmsJson[2] =  "{\"self_phone\":\"+333333333333333\",\"address\":" +
160                 "\"+1232221412433 +1232221412444\",\"body\":\"sms 3\",\"date\":\"111111111111\"," +
161                 "\"date_sent\":" +
162                 "\"999999999\",\"status\":\"2\",\"type\":\"3\"," +
163                 "\"recipients\":[\"+1232221412433\",\"+1232221412444\"],\"read\":\"0\"}";
164         mThreadProvider.getOrCreateThreadId(new String[]{"+1232221412433", "+1232221412444"});
165 
166 
167         mSmsRows[3] = createSmsRow(5, 3, null, "sms 4", null,
168                 111111111111l, 999999999, 2, 3, 5, false);
169         mSmsJson[3] = "{\"self_phone\":\"+333333333333333\"," +
170                 "\"body\":\"sms 4\",\"date\":\"111111111111\"," +
171                 "\"date_sent\":" +
172                 "\"999999999\",\"status\":\"2\",\"type\":\"3\",\"read\":\"0\"}";
173 
174         mAllSmsJson = makeJsonArray(mSmsJson);
175 
176 
177 
178         mMmsRows = new ContentValues[3];
179         mMmsJson = new String[3];
180         mMmsRows[0] = createMmsRow(1 /*id*/, 1 /*subid*/, "Subject 1" /*subject*/,
181                 100 /*subcharset*/, 111111 /*date*/, 111112 /*datesent*/, 3 /*type*/,
182                 17 /*version*/, 1 /*textonly*/,
183                 11 /*msgBox*/, "location 1" /*contentLocation*/, "MMs body 1" /*body*/,
184                 111 /*body charset*/,
185                 new String[]{"+111 (111) 11111111", "+11121212", "example@example.com",
186                         "+999999999"} /*addresses*/,
187                 3 /*threadId*/, false /*read*/, null /*smil*/, null /*attachmentTypes*/,
188                 null /*attachmentFilenames*/, mMmsAllContentValues);
189 
190         mMmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"sub\":\"Subject 1\"," +
191                 "\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":\"3\",\"v\":\"17\"," +
192                 "\"msg_box\":\"11\",\"ct_l\":\"location 1\"," +
193                 "\"recipients\":[\"+11121212\",\"example@example.com\",\"+999999999\"]," +
194                 "\"read\":\"0\"," +
195                 "\"mms_addresses\":" +
196                 "[{\"type\":10,\"address\":\"+111 (111) 11111111\",\"charset\":100}," +
197                 "{\"type\":11,\"address\":\"+11121212\",\"charset\":101},{\"type\":12,\"address\":"+
198                 "\"example@example.com\",\"charset\":102},{\"type\":13,\"address\":\"+999999999\"" +
199                 ",\"charset\":103}],\"mms_body\":\"MMs body 1\",\"mms_charset\":111,\"" +
200                 "sub_cs\":\"100\"}";
201         mThreadProvider.getOrCreateThreadId(new String[]{"+11121212", "example@example.com",
202                 "+999999999"});
203 
204         mMmsRows[1] = createMmsRow(2 /*id*/, 2 /*subid*/, null /*subject*/, 100 /*subcharset*/,
205                 111122 /*date*/, 1111112 /*datesent*/, 4 /*type*/, 18 /*version*/, 1 /*textonly*/,
206                 222 /*msgBox*/, "location 2" /*contentLocation*/, "MMs body 2" /*body*/,
207                 121 /*body charset*/,
208                 new String[]{"+7 (333) ", "example@example.com", "+999999999"} /*addresses*/,
209                 4 /*threadId*/, true /*read*/, null /*smil*/, null /*attachmentTypes*/,
210                 null /*attachmentFilenames*/, mMmsAllContentValues);
211         mMmsJson[1] = "{\"date\":\"111122\",\"date_sent\":\"1111112\",\"m_type\":\"4\"," +
212                 "\"v\":\"18\",\"msg_box\":\"222\",\"ct_l\":\"location 2\"," +
213                 "\"recipients\":[\"example@example.com\",\"+999999999\"]," +
214                 "\"read\":\"1\"," +
215                 "\"mms_addresses\":" +
216                 "[{\"type\":10,\"address\":\"+7 (333) \",\"charset\":100}," +
217                 "{\"type\":11,\"address\":\"example@example.com\",\"charset\":101}," +
218                 "{\"type\":12,\"address\":\"+999999999\",\"charset\":102}]," +
219                 "\"mms_body\":\"MMs body 2\",\"mms_charset\":121}";
220         mThreadProvider.getOrCreateThreadId(new String[]{"example@example.com", "+999999999"});
221 
222         mMmsRows[2] = createMmsRow(9 /*id*/, 3 /*subid*/, "Subject 10" /*subject*/,
223                 10 /*subcharset*/, 111133 /*date*/, 1111132 /*datesent*/, 5 /*type*/,
224                 19 /*version*/, 1 /*textonly*/,
225                 333 /*msgBox*/, null /*contentLocation*/, "MMs body 3" /*body*/,
226                 131 /*body charset*/,
227                 new String[]{"333 333333333333", "+1232132214124"} /*addresses*/,
228                 1 /*threadId*/, false /*read*/, null /*smil*/, null /*attachmentTypes*/,
229                 null /*attachmentFilenames*/, mMmsAllContentValues);
230 
231         mMmsJson[2] = "{\"self_phone\":\"+333333333333333\",\"sub\":\"Subject 10\"," +
232                 "\"date\":\"111133\",\"date_sent\":\"1111132\",\"m_type\":\"5\",\"v\":\"19\"," +
233                 "\"msg_box\":\"333\"," +
234                 "\"recipients\":[\"+123 (213) 2214124\"],\"archived\":true," +
235                 "\"read\":\"0\"," +
236                 "\"mms_addresses\":" +
237                 "[{\"type\":10,\"address\":\"333 333333333333\",\"charset\":100}," +
238                 "{\"type\":11,\"address\":\"+1232132214124\",\"charset\":101}]," +
239                 "\"mms_body\":\"MMs body 3\",\"mms_charset\":131," +
240                 "\"sub_cs\":\"10\"}";
241         mAllMmsJson = makeJsonArray(mMmsJson);
242 
243 
244         mMmsAttachmentRows = new ContentValues[1];
245         mMmsAttachmentJson = new String[1];
246         mMmsAttachmentRows[0] = createMmsRow(1 /*id*/, 1 /*subid*/, "Subject 1" /*subject*/,
247                 100 /*subcharset*/, 111111 /*date*/, 111112 /*datesent*/, 3 /*type*/,
248                 17 /*version*/, 0 /*textonly*/,
249                 11 /*msgBox*/, "location 1" /*contentLocation*/, "MMs body 1" /*body*/,
250                 111 /*body charset*/,
251                 new String[]{"+111 (111) 11111111", "+11121212", "example@example.com",
252                         "+999999999"} /*addresses*/,
253                 3 /*threadId*/, false /*read*/, "<smil><head><layout><root-layout/>"
254                         + "<region id='Image' fit='meet' top='0' left='0' height='100%'"
255                         + " width='100%'/></layout></head><body><par dur='5000ms'>"
256                         + "<img src='image000000.jpg' region='Image' /></par></body></smil>",
257                 new String[] {"image/jpg"} /*attachmentTypes*/,
258                 new String[] {"GreatPict.jpg"}  /*attachmentFilenames*/, mMmsAllContentValues);
259 
260         mMmsAttachmentJson[0] = "{\"self_phone\":\"+111111111111111\",\"sub\":\"Subject 1\"," +
261                 "\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":\"3\",\"v\":\"17\"," +
262                 "\"msg_box\":\"11\",\"ct_l\":\"location 1\"," +
263                 "\"recipients\":[\"+11121212\",\"example@example.com\",\"+999999999\"]," +
264                 "\"read\":\"0\"," +
265                 "\"mms_addresses\":" +
266                 "[{\"type\":10,\"address\":\"+111 (111) 11111111\",\"charset\":100}," +
267                 "{\"type\":11,\"address\":\"+11121212\",\"charset\":101},{\"type\":12,\"address\":"+
268                 "\"example@example.com\",\"charset\":102},{\"type\":13,\"address\":\"+999999999\"" +
269                 ",\"charset\":103}],\"mms_body\":\"MMs body 1\",\"mms_charset\":111,\"" +
270                 "sub_cs\":\"100\"}";
271 
272         mMmsAllAttachmentJson = makeJsonArray(mMmsAttachmentJson);
273 
274         createMmsRow(10 /*id*/, 1 /*subid*/, "Subject 1" /*subject*/,
275                 100 /*subcharset*/, 111111 /*date*/, 111112 /*datesent*/, 3 /*type*/,
276                 17 /*version*/, 0 /*textonly*/,
277                 11 /*msgBox*/, "location 1" /*contentLocation*/, "" /*body*/,
278                 CharacterSets.DEFAULT_CHARSET /*body charset*/, new String[] {} /*addresses*/,
279                 3 /*threadId*/, false /*read*/, null /*smil*/, null /*attachmentTypes*/,
280                 null /*attachmentFilenames*/, mMmsNullBodyContentValues);
281 
282         mMmsAllNullBodyJson = makeJsonArray(new String[] {"{\"self_phone\":\"+111111111111111\"," +
283                 "\"sub\":\"Subject 1\",\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":" +
284                 "\"3\",\"v\":\"17\",\"msg_box\":\"11\",\"ct_l\":\"location 1\"," +
285                 "\"recipients\":[\"+11121212\",\"example@example.com\",\"+999999999\"]," +
286                 "\"read\":\"0\", \"mms_addresses\":[],\"mms_charset\":111,\"sub_cs\":\"100\"}"});
287 
288 
289         ContentProvider contentProvider = new MockContentProvider() {
290             @Override
291             public Cursor query(Uri uri, String[] projection, String selection,
292                                 String[] selectionArgs, String sortOrder) {
293                 if (mCursors.containsKey(uri)) {
294                     FakeCursor fakeCursor = mCursors.get(uri);
295                     if (projection != null) {
296                         fakeCursor.setProjection(projection);
297                     }
298                     fakeCursor.nextRow = 0;
299                     return fakeCursor;
300                 }
301                 fail("No cursor for " + uri.toString());
302                 return null;
303             }
304         };
305 
306         mMockContentResolver.addProvider("sms", contentProvider);
307         mMockContentResolver.addProvider("mms", contentProvider);
308         mMockContentResolver.addProvider("mms-sms", mThreadProvider);
309 
310         mTelephonyBackupAgent = new TelephonyBackupAgent();
311         mTelephonyBackupAgent.attach(new ContextWrapper(getContext()) {
312             @Override
313             public ContentResolver getContentResolver() {
314                 return mMockContentResolver;
315             }
316         });
317 
318 
319         mTelephonyBackupAgent.clearSharedPreferences();
320         mTelephonyBackupAgent.setContentResolver(mMockContentResolver);
321         mTelephonyBackupAgent.setSubId(mSubId2Phone, mPhone2SubId);
322     }
323 
324     @Override
tearDown()325     protected void tearDown() throws Exception {
326         mTelephonyBackupAgent.clearSharedPreferences();
327         super.tearDown();
328     }
329 
makeJsonArray(String[] json)330     private static String makeJsonArray(String[] json) {
331         StringBuilder stringBuilder = new StringBuilder("[");
332         for (int i=0; i<json.length; ++i) {
333             if (i > 0) {
334                 stringBuilder.append(",");
335             }
336             stringBuilder.append(json[i]);
337         }
338         stringBuilder.append("]");
339         return stringBuilder.toString();
340     }
341 
createSmsRow(int id, int subId, String address, String body, String subj, long date, long dateSent, int status, int type, long threadId, boolean read)342     private static ContentValues createSmsRow(int id, int subId, String address, String body,
343                                               String subj, long date, long dateSent,
344                                               int status, int type, long threadId,
345                                               boolean read) {
346         ContentValues smsRow = new ContentValues();
347         smsRow.put(Telephony.Sms._ID, id);
348         smsRow.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
349         if (address != null) {
350             smsRow.put(Telephony.Sms.ADDRESS, address);
351         }
352         if (body != null) {
353             smsRow.put(Telephony.Sms.BODY, body);
354         }
355         if (subj != null) {
356             smsRow.put(Telephony.Sms.SUBJECT, subj);
357         }
358         smsRow.put(Telephony.Sms.DATE, String.valueOf(date));
359         smsRow.put(Telephony.Sms.DATE_SENT, String.valueOf(dateSent));
360         smsRow.put(Telephony.Sms.STATUS, String.valueOf(status));
361         smsRow.put(Telephony.Sms.TYPE, String.valueOf(type));
362         smsRow.put(Telephony.Sms.THREAD_ID, threadId);
363         smsRow.put(Telephony.Sms.READ, read ? "1" : "0");
364 
365         return smsRow;
366     }
367 
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, boolean read, String smil, String[] attachmentTypes, String[] attachmentFilenames, List<ContentValues> rowsContainer)368     private ContentValues createMmsRow(int id, int subId, String subj, int subCharset,
369                                        long date, long dateSent, int type, int version,
370                                        int textOnly, int msgBox,
371                                        String contentLocation, String body,
372                                        int bodyCharset, String[] addresses, long threadId,
373                                        boolean read, String smil, String[] attachmentTypes,
374                                        String[] attachmentFilenames,
375                                        List<ContentValues> rowsContainer) {
376         ContentValues mmsRow = new ContentValues();
377         mmsRow.put(Telephony.Mms._ID, id);
378         mmsRow.put(Telephony.Mms.SUBSCRIPTION_ID, subId);
379         if (subj != null) {
380             mmsRow.put(Telephony.Mms.SUBJECT, subj);
381             mmsRow.put(Telephony.Mms.SUBJECT_CHARSET, String.valueOf(subCharset));
382         }
383         mmsRow.put(Telephony.Mms.DATE, String.valueOf(date));
384         mmsRow.put(Telephony.Mms.DATE_SENT, String.valueOf(dateSent));
385         mmsRow.put(Telephony.Mms.MESSAGE_TYPE, String.valueOf(type));
386         mmsRow.put(Telephony.Mms.MMS_VERSION, String.valueOf(version));
387         mmsRow.put(Telephony.Mms.TEXT_ONLY, textOnly);
388         mmsRow.put(Telephony.Mms.MESSAGE_BOX, String.valueOf(msgBox));
389         if (contentLocation != null) {
390             mmsRow.put(Telephony.Mms.CONTENT_LOCATION, contentLocation);
391         }
392         mmsRow.put(Telephony.Mms.THREAD_ID, threadId);
393         mmsRow.put(Telephony.Mms.READ, read ? "1" : "0");
394 
395         final Uri partUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
396                 appendPath("part").build();
397         mCursors.put(partUri, createBodyCursor(body, bodyCharset, smil, attachmentTypes,
398                 attachmentFilenames, rowsContainer));
399         rowsContainer.add(mmsRow);
400 
401         final Uri addrUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
402                 appendPath("addr").build();
403         mCursors.put(addrUri, createAddrCursor(addresses));
404 
405         return mmsRow;
406     }
407 
408     private static final String APP_SMIL = "application/smil";
409     private static final String TEXT_PLAIN = "text/plain";
410     private static final String IMAGE_JPG = "image/jpg";
411 
412     // Cursor with parts of Mms.
createBodyCursor(String body, int charset, String existingSmil, String[] attachmentTypes, String[] attachmentFilenames, List<ContentValues> rowsContainer)413     private FakeCursor createBodyCursor(String body, int charset, String existingSmil,
414             String[] attachmentTypes, String[] attachmentFilenames,
415             List<ContentValues> rowsContainer) {
416         List<ContentValues> table = new ArrayList<>();
417         final String srcName = String.format("text.%06d.txt", 0);
418         final String smilBody = TextUtils.isEmpty(existingSmil) ?
419                 String.format(TelephonyBackupAgent.sSmilTextPart, srcName) : existingSmil;
420         final String smil = String.format(TelephonyBackupAgent.sSmilTextOnly, smilBody);
421 
422         // SMIL
423         final ContentValues smilPart = new ContentValues();
424         smilPart.put(Telephony.Mms.Part.SEQ, -1);
425         smilPart.put(Telephony.Mms.Part.CONTENT_TYPE, APP_SMIL);
426         smilPart.put(Telephony.Mms.Part.NAME, "smil.xml");
427         smilPart.put(Telephony.Mms.Part.CONTENT_ID, "<smil>");
428         smilPart.put(Telephony.Mms.Part.CONTENT_LOCATION, "smil.xml");
429         smilPart.put(Telephony.Mms.Part.TEXT, smil);
430         rowsContainer.add(smilPart);
431 
432         // Text part
433         final ContentValues bodyPart = new ContentValues();
434         bodyPart.put(Telephony.Mms.Part.SEQ, 0);
435         bodyPart.put(Telephony.Mms.Part.CONTENT_TYPE, TEXT_PLAIN);
436         bodyPart.put(Telephony.Mms.Part.NAME, srcName);
437         bodyPart.put(Telephony.Mms.Part.CONTENT_ID, "<"+srcName+">");
438         bodyPart.put(Telephony.Mms.Part.CONTENT_LOCATION, srcName);
439         bodyPart.put(Telephony.Mms.Part.CHARSET, charset);
440         bodyPart.put(Telephony.Mms.Part.TEXT, body);
441         table.add(bodyPart);
442         rowsContainer.add(bodyPart);
443 
444         // Attachments
445         if (attachmentTypes != null) {
446             for (int i = 0; i < attachmentTypes.length; i++) {
447                 String attachmentType = attachmentTypes[i];
448                 String attachmentFilename = attachmentFilenames[i];
449                 final ContentValues attachmentPart = new ContentValues();
450                 attachmentPart.put(Telephony.Mms.Part.SEQ, i + 1);
451                 attachmentPart.put(Telephony.Mms.Part.CONTENT_TYPE, attachmentType);
452                 attachmentPart.put(Telephony.Mms.Part.NAME, attachmentFilename);
453                 attachmentPart.put(Telephony.Mms.Part.CONTENT_ID, "<"+attachmentFilename+">");
454                 attachmentPart.put(Telephony.Mms.Part.CONTENT_LOCATION, attachmentFilename);
455                 table.add(attachmentPart);
456                 rowsContainer.add(attachmentPart);
457             }
458         }
459 
460         return new FakeCursor(table, TelephonyBackupAgent.MMS_TEXT_PROJECTION);
461     }
462 
463     // Cursor with addresses of Mms.
createAddrCursor(String[] addresses)464     private FakeCursor createAddrCursor(String[] addresses) {
465         List<ContentValues> table = new ArrayList<>();
466         for (int i=0; i<addresses.length; ++i) {
467             ContentValues addr = new ContentValues();
468             addr.put(Telephony.Mms.Addr.TYPE, 10+i);
469             addr.put(Telephony.Mms.Addr.ADDRESS, addresses[i]);
470             addr.put(Telephony.Mms.Addr.CHARSET, 100+i);
471             mMmsAllContentValues.add(addr);
472             table.add(addr);
473         }
474         return new FakeCursor(table, TelephonyBackupAgent.MMS_ADDR_PROJECTION);
475     }
476 
477     /**
478      * Test with no sms in the provider.
479      * @throws Exception
480      */
testBackupSms_NoSms()481     public void testBackupSms_NoSms() throws Exception {
482         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
483         assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
484     }
485 
486     /**
487      * Test with 3 sms in the provider with the limit per file 4.
488      * @throws Exception
489      */
testBackupSms_AllSms()490     public void testBackupSms_AllSms() throws Exception {
491         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
492         mSmsTable.addAll(Arrays.asList(mSmsRows));
493         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
494         assertEquals(mAllSmsJson, mStringWriter.toString());
495     }
496 
497     /**
498      * Test with 3 sms in the provider with the limit per file 3.
499      * @throws Exception
500      */
testBackupSms_AllSmsWithExactFileLimit()501     public void testBackupSms_AllSmsWithExactFileLimit() throws Exception {
502         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
503         mSmsTable.addAll(Arrays.asList(mSmsRows));
504         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
505         assertEquals(mAllSmsJson, mStringWriter.toString());
506     }
507 
508     /**
509      * Test with 3 sms in the provider with the limit per file 1.
510      * @throws Exception
511      */
testBackupSms_AllSmsOneMessagePerFile()512     public void testBackupSms_AllSmsOneMessagePerFile() throws Exception {
513         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
514         mSmsTable.addAll(Arrays.asList(mSmsRows));
515 
516         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
517         assertEquals("[" + mSmsJson[0] + "]", mStringWriter.toString());
518 
519         mStringWriter = new StringWriter();
520         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
521         assertEquals("[" + mSmsJson[1] + "]", mStringWriter.toString());
522 
523         mStringWriter = new StringWriter();
524         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
525         assertEquals("[" + mSmsJson[2] + "]", mStringWriter.toString());
526 
527         mStringWriter = new StringWriter();
528         mTelephonyBackupAgent.putSmsMessagesToJson(mSmsCursor, new JsonWriter(mStringWriter));
529         assertEquals("[" + mSmsJson[3] + "]", mStringWriter.toString());
530     }
531 
532     /**
533      * Test with no mms in the pvovider.
534      * @throws Exception
535      */
testBackupMms_NoMms()536     public void testBackupMms_NoMms() throws Exception {
537         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
538         assertEquals(EMPTY_JSON_ARRAY, mStringWriter.toString());
539     }
540 
541     /**
542      * Test with all mms.
543      * @throws Exception
544      */
testBackupMms_AllMms()545     public void testBackupMms_AllMms() throws Exception {
546         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
547         mMmsTable.addAll(Arrays.asList(mMmsRows));
548         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
549         assertEquals(mAllMmsJson, mStringWriter.toString());
550     }
551 
552     /**
553      * Test with attachment mms.
554      * @throws Exception
555      */
testBackupMmsWithAttachmentMms()556     public void testBackupMmsWithAttachmentMms() throws Exception {
557         mTelephonyBackupAgent.mMaxMsgPerFile = 4;
558         mMmsTable.addAll(Arrays.asList(mMmsAttachmentRows));
559         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
560         assertEquals(mMmsAllAttachmentJson, mStringWriter.toString());
561     }
562 
563     /**
564      * Test with 3 mms in the provider with the limit per file 1.
565      * @throws Exception
566      */
testBackupMms_OneMessagePerFile()567     public void testBackupMms_OneMessagePerFile() throws Exception {
568         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
569         mMmsTable.addAll(Arrays.asList(mMmsRows));
570         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
571         assertEquals("[" + mMmsJson[0] + "]", mStringWriter.toString());
572 
573         mStringWriter = new StringWriter();
574         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
575         assertEquals("[" + mMmsJson[1] + "]", mStringWriter.toString());
576 
577         mStringWriter = new StringWriter();
578         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
579         assertEquals("[" + mMmsJson[2] + "]", mStringWriter.toString());
580     }
581 
582     /**
583      * Test with 3 mms in the provider with the limit per file 3.
584      * @throws Exception
585      */
testBackupMms_WithExactFileLimit()586     public void testBackupMms_WithExactFileLimit() throws Exception {
587         mMmsTable.addAll(Arrays.asList(mMmsRows));
588         mTelephonyBackupAgent.mMaxMsgPerFile = 3;
589         mTelephonyBackupAgent.putMmsMessagesToJson(mMmsCursor, new JsonWriter(mStringWriter));
590         assertEquals(mAllMmsJson, mStringWriter.toString());
591     }
592 
593     /**
594      * Test restore sms with the empty json array "[]".
595      * @throws Exception
596      */
testRestoreSms_NoSms()597     public void testRestoreSms_NoSms() throws Exception {
598         JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
599         FakeSmsProvider smsProvider = new FakeSmsProvider(null);
600         mMockContentResolver.addProvider("sms", smsProvider);
601         mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
602         assertEquals(0, smsProvider.getRowsAdded());
603     }
604 
605     /**
606      * Test restore sms with three sms json object in the array.
607      * @throws Exception
608      */
testRestoreSms_AllSms()609     public void testRestoreSms_AllSms() throws Exception {
610         mTelephonyBackupAgent.initUnknownSender();
611         JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
612         FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows);
613         mMockContentResolver.addProvider("sms", smsProvider);
614         mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
615         assertEquals(mSmsRows.length, smsProvider.getRowsAdded());
616         assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
617     }
618 
619     /**
620      * Test that crashing for one sms does not block restore of other messages.
621      * @throws Exception
622      */
testRestoreSms_WithException()623     public void testRestoreSms_WithException() throws Exception {
624         mTelephonyBackupAgent.initUnknownSender();
625         PhoneFactory.addLocalLog("DeferredSmsMmsRestoreService", 1);
626         JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
627         FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows, false);
628         mMockContentResolver.addProvider("sms", smsProvider);
629         TelephonyBackupAgent.SmsProviderQuery smsProviderQuery =
630                 new TelephonyBackupAgent.SmsProviderQuery() {
631                     int mIteration = 0;
632                     @Override
633                     public boolean doesSmsExist(ContentValues smsValues) {
634                         if (mIteration == 0) {
635                             mIteration++;
636                             throw new RuntimeException("fake crash for first message");
637                         }
638                         return false;
639                     }
640         };
641         TelephonyBackupAgent.SmsProviderQuery previousQuery =
642                 mTelephonyBackupAgent.getAndSetSmsProviderQuery(smsProviderQuery);
643         try {
644             mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
645             // the "- 1" is due to exception thrown for one of the messages
646             assertEquals(mSmsRows.length - 1, smsProvider.getRowsAdded());
647             assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
648         } finally {
649             mTelephonyBackupAgent.getAndSetSmsProviderQuery(previousQuery);
650         }
651     }
652 
653     /**
654      * Test restore mms with the empty json array "[]".
655      * @throws Exception
656      */
testRestoreMms_NoMms()657     public void testRestoreMms_NoMms() throws Exception {
658         JsonReader jsonReader = new JsonReader(new StringReader(EMPTY_JSON_ARRAY));
659         FakeMmsProvider mmsProvider = new FakeMmsProvider(null);
660         mMockContentResolver.addProvider("mms", mmsProvider);
661         mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
662         assertEquals(0, mmsProvider.getRowsAdded());
663     }
664 
665     /**
666      * Test restore mms with three mms json object in the array.
667      * @throws Exception
668      */
testRestoreMms_AllMms()669     public void testRestoreMms_AllMms() throws Exception {
670         JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllMmsJson)));
671         FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsAllContentValues);
672         mMockContentResolver.addProvider("mms", mmsProvider);
673         mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
674         assertEquals(18, mmsProvider.getRowsAdded());
675         assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
676     }
677 
678     /**
679      * Test restore a single mms with an attachment.
680      * @throws Exception
681      */
testRestoreMmsWithAttachment()682     public void testRestoreMmsWithAttachment() throws Exception {
683         JsonReader jsonReader = new JsonReader
684                 (new StringReader(addRandomDataToJson(mMmsAllAttachmentJson)));
685         FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsAllContentValues);
686         mMockContentResolver.addProvider("mms", mmsProvider);
687         mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
688         assertEquals(7, mmsProvider.getRowsAdded());
689     }
690 
testRestoreMmsWithNullBody()691     public void testRestoreMmsWithNullBody() throws Exception {
692         JsonReader jsonReader = new JsonReader
693                 (new StringReader(addRandomDataToJson(mMmsAllNullBodyJson)));
694         FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsNullBodyContentValues);
695         mMockContentResolver.addProvider("mms", mmsProvider);
696 
697         mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
698 
699         assertEquals(3, mmsProvider.getRowsAdded());
700     }
701 
702     /**
703      * Test with quota exceeded. Checking size of the backup before it hits quota and after.
704      * It still backs up more than a quota since there is meta-info which matters with small amounts
705      * of data. The agent does not take backup meta-info into consideration.
706      * @throws Exception
707      */
testBackup_WithQuotaExceeded()708     public void testBackup_WithQuotaExceeded() throws Exception {
709         mTelephonyBackupAgent.mMaxMsgPerFile = 1;
710         final int backupSize = 7168;
711         final int backupSizeAfterFirstQuotaHit = 6144;
712         final int backupSizeAfterSecondQuotaHit = 5120;
713 
714         mSmsTable.addAll(Arrays.asList(mSmsRows));
715         mMmsTable.addAll(Arrays.asList(mMmsRows));
716 
717         FullBackupDataOutput fullBackupDataOutput = new FullBackupDataOutput(Long.MAX_VALUE);
718         mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
719         assertEquals(backupSize, fullBackupDataOutput.getSize());
720 
721         mTelephonyBackupAgent.onQuotaExceeded(backupSize, backupSize - 100);
722         fullBackupDataOutput = new FullBackupDataOutput(Long.MAX_VALUE);
723         mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
724         assertEquals(backupSizeAfterFirstQuotaHit, fullBackupDataOutput.getSize());
725 
726         mTelephonyBackupAgent.onQuotaExceeded(backupSizeAfterFirstQuotaHit,
727                 backupSizeAfterFirstQuotaHit - 200);
728         fullBackupDataOutput = new FullBackupDataOutput(Long.MAX_VALUE);
729         mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
730         assertEquals(backupSizeAfterSecondQuotaHit, fullBackupDataOutput.getSize());
731     }
732 
733     /**
734      * Test backups are consistent between runs. This ensures that when no data
735      * has changed between backup runs we don't generate a diff which needs to
736      * be sent to the server.
737      * @throws Exception
738      */
testBackup_WithoutChanges_DoesNotChangeOutput()739     public void testBackup_WithoutChanges_DoesNotChangeOutput() throws Exception {
740         mSmsTable.addAll(Arrays.asList(mSmsRows));
741         mMmsTable.addAll(Arrays.asList(mMmsRows));
742 
743         byte[] firstBackup = getBackup("1");
744         // Ensure there is some time between backup runs. This is the way to identify
745         // time dependent backup contents.
746         Thread.sleep(TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS));
747         byte[] secondBackup = getBackup("2");
748 
749         // Make sure something has been backed up.
750         assertFalse(firstBackup == null || firstBackup.length == 0);
751 
752         // Make sure the two backups are the same.
753         assertArrayEquals(firstBackup, secondBackup);
754     }
755 
getBackup(String runId)756     private byte[] getBackup(String runId) throws IOException {
757         File cacheDir = getContext().getCacheDir();
758         File backupOutput = File.createTempFile("backup", runId, cacheDir);
759         ParcelFileDescriptor outputFd =
760                 ParcelFileDescriptor.open(backupOutput, ParcelFileDescriptor.MODE_WRITE_ONLY);
761         try {
762             FullBackupDataOutput fullBackupDataOutput = new FullBackupDataOutput(outputFd);
763             mTelephonyBackupAgent.onFullBackup(fullBackupDataOutput);
764             return IoUtils.readFileAsByteArray(backupOutput.getAbsolutePath());
765         } finally {
766             outputFd.close();
767             backupOutput.delete();
768         }
769     }
770 
771     // Adding random keys to JSON to test handling it by the BackupAgent on restore.
addRandomDataToJson(String jsonString)772     private String addRandomDataToJson(String jsonString) throws JSONException {
773         JSONArray jsonArray = new JSONArray(jsonString);
774         JSONArray res = new JSONArray();
775         for (int i = 0; i < jsonArray.length(); ++i) {
776             JSONObject jsonObject = jsonArray.getJSONObject(i);
777             jsonObject.put(UUID.randomUUID().toString(), UUID.randomUUID().toString());
778             res = res.put(jsonObject);
779         }
780         return res.toString();
781     }
782 
783     /**
784      * class for checking sms insertion into the provider on restore.
785      */
786     private class FakeSmsProvider extends MockContentProvider {
787         private int nextRow = 0;
788         private ContentValues[] mSms;
789         private boolean mCheckInsertedValues = true;
790 
FakeSmsProvider(ContentValues[] sms)791         public FakeSmsProvider(ContentValues[] sms) {
792             this.mSms = sms;
793         }
794 
FakeSmsProvider(ContentValues[] sms, boolean checkInsertedValues)795         public FakeSmsProvider(ContentValues[] sms, boolean checkInsertedValues) {
796             this.mSms = sms;
797             mCheckInsertedValues = checkInsertedValues;
798         }
799 
800         @Override
insert(Uri uri, ContentValues values)801         public Uri insert(Uri uri, ContentValues values) {
802             assertEquals(Telephony.Sms.CONTENT_URI, uri);
803             ContentValues modifiedValues = new ContentValues(mSms[nextRow++]);
804             modifiedValues.remove(Telephony.Sms._ID);
805             modifiedValues.put(Telephony.Sms.SEEN, 1);
806             if (mSubId2Phone.get(modifiedValues.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID))
807                     == null) {
808                 modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
809             }
810 
811             if (modifiedValues.get(Telephony.Sms.ADDRESS) == null) {
812                 modifiedValues.put(Telephony.Sms.ADDRESS, TelephonyBackupAgent.UNKNOWN_SENDER);
813             }
814 
815             if (mCheckInsertedValues) assertEquals(modifiedValues, values);
816             return null;
817         }
818 
819         @Override
bulkInsert(Uri uri, ContentValues[] values)820         public int bulkInsert(Uri uri, ContentValues[] values) {
821             for (ContentValues cv : values) {
822                 insert(uri, cv);
823             }
824             return values.length;
825         }
826 
827         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)828         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
829                             String sortOrder) {
830             return null;
831         }
832 
getRowsAdded()833         public int getRowsAdded() {
834             return nextRow;
835         }
836     }
837 
838     /**
839      * class for checking mms insertion into the provider on restore.
840      */
841     private class FakeMmsProvider extends MockContentProvider {
842         private int nextRow = 0;
843         private List<ContentValues> mValues;
844         private long mPlaceholderMsgId = -1;
845         private long mMsgId = -1;
846         private String mFilename;
847 
FakeMmsProvider(List<ContentValues> values)848         public FakeMmsProvider(List<ContentValues> values) {
849             this.mValues = values;
850         }
851 
852         @Override
insert(Uri uri, ContentValues values)853         public Uri insert(Uri uri, ContentValues values) {
854             Uri retUri = Uri.parse("test_uri");
855             ContentValues modifiedValues = new ContentValues(mValues.get(nextRow++));
856             if (values.containsKey("read")) {
857                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
858             }
859             if (modifiedValues.containsKey("read")) {
860                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
861             }
862             if (APP_SMIL.equals(values.get(Telephony.Mms.Part.CONTENT_TYPE))) {
863                 // Smil part.
864                 assertEquals(-1, mPlaceholderMsgId);
865                 mPlaceholderMsgId = values.getAsLong(Telephony.Mms.Part.MSG_ID);
866             }
867             if (IMAGE_JPG.equals(values.get(Telephony.Mms.Part.CONTENT_TYPE))) {
868                 // Image attachment part.
869                 mFilename = values.getAsString(Telephony.Mms.Part.CONTENT_LOCATION);
870                 String path = values.getAsString(Telephony.Mms.Part._DATA);
871                 assertTrue(path.endsWith(mFilename));
872             }
873             if (values.containsKey("read")) {
874                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
875             }
876             if (modifiedValues.containsKey("read")) {
877                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
878             }
879 
880             if (values.get(Telephony.Mms.Part.SEQ) != null) {
881                 // Part of mms.
882                 final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
883                         .appendPath(String.valueOf(mPlaceholderMsgId))
884                         .appendPath("part")
885                         .build();
886                 assertEquals(expectedUri, uri);
887             }
888             if (values.containsKey("read")) {
889                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
890             }
891             if (modifiedValues.containsKey("read")) {
892                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
893             }
894 
895             if (values.get(Telephony.Mms.Part.MSG_ID) != null) {
896                 modifiedValues.put(Telephony.Mms.Part.MSG_ID, mPlaceholderMsgId);
897             }
898             if (values.containsKey("read")) {
899                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
900             }
901             if (modifiedValues.containsKey("read")) {
902                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
903             }
904 
905 
906             if (values.get(Telephony.Mms.SUBSCRIPTION_ID) != null) {
907                 assertEquals(Telephony.Mms.CONTENT_URI, uri);
908                 if (mSubId2Phone.get(modifiedValues.getAsInteger(Telephony.Sms.SUBSCRIPTION_ID))
909                         == null) {
910                     modifiedValues.put(Telephony.Sms.SUBSCRIPTION_ID, -1);
911                 }
912                 // Mms.
913                 modifiedValues.put(Telephony.Mms.SEEN, 1);
914                 mMsgId = modifiedValues.getAsInteger(BaseColumns._ID);
915                 retUri = Uri.withAppendedPath(Telephony.Mms.CONTENT_URI, String.valueOf(mMsgId));
916                 modifiedValues.remove(BaseColumns._ID);
917             }
918             if (values.containsKey("read")) {
919                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
920             }
921             if (modifiedValues.containsKey("read")) {
922                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
923             }
924 
925             if (values.get(Telephony.Mms.Addr.ADDRESS) != null) {
926                 // Address.
927                 final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
928                         .appendPath(String.valueOf(mMsgId))
929                         .appendPath("addr")
930                         .build();
931                 assertEquals(expectedUri, uri);
932                 assertNotSame(-1, mMsgId);
933                 modifiedValues.put(Telephony.Mms.Addr.MSG_ID, mMsgId);
934                 mPlaceholderMsgId = -1;
935             }
936             if (values.containsKey("read")) {
937                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
938             }
939             if (modifiedValues.containsKey("read")) {
940                 assertEquals("read: ", modifiedValues.get("read"), values.get("read"));
941             }
942 
943             for (String key : modifiedValues.keySet()) {
944                 assertEquals("Key:"+key, modifiedValues.get(key), values.get(key));
945             }
946             assertEquals(modifiedValues.size(), values.size());
947             return retUri;
948         }
949 
950         @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)951         public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
952             final Uri expectedUri = Telephony.Mms.CONTENT_URI.buildUpon()
953                     .appendPath(String.valueOf(mPlaceholderMsgId))
954                     .appendPath("part")
955                     .build();
956             assertEquals(expectedUri, uri);
957             ContentValues expected = new ContentValues();
958             expected.put(Telephony.Mms.Part.MSG_ID, mMsgId);
959             assertEquals(expected, values);
960             return 2;
961         }
962 
963         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)964         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
965                             String sortOrder) {
966             return null;
967         }
968 
getRowsAdded()969         public int getRowsAdded() {
970             return nextRow;
971         }
972     }
973 
974     /**
975      * class that implements MmsSms provider for thread ids.
976      */
977     private static class ThreadProvider extends MockContentProvider {
978         ArrayList<Set<Integer> > id2Thread = new ArrayList<>();
979         ArrayList<String> id2Recipient = new ArrayList<>();
980         Set<Integer> mIsThreadArchived = new HashSet<>();
981         Set<Integer> mUpdateThreadsArchived = new HashSet<>();
982 
983 
getOrCreateThreadId(final String[] recipients)984         public int getOrCreateThreadId(final String[] recipients) {
985             if (recipients == null || recipients.length == 0) {
986                 throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
987             }
988 
989             Set<Integer> ids = new ArraySet<>();
990             for (String rec : recipients) {
991                 if (!id2Recipient.contains(rec)) {
992                     id2Recipient.add(rec);
993                 }
994                 ids.add(id2Recipient.indexOf(rec)+1);
995             }
996             if (!id2Thread.contains(ids)) {
997                 id2Thread.add(ids);
998             }
999             return id2Thread.indexOf(ids)+1;
1000         }
1001 
setArchived(int threadId)1002         public void setArchived(int threadId) {
1003             mIsThreadArchived.add(threadId);
1004         }
1005 
getSpaceSepIds(int threadId)1006         private String getSpaceSepIds(int threadId) {
1007             if (id2Thread.size() < threadId) {
1008                 return null;
1009             }
1010 
1011             String spaceSepIds = null;
1012             for (Integer id : id2Thread.get(threadId-1)) {
1013                 spaceSepIds = (spaceSepIds == null ? "" : spaceSepIds + " ") + String.valueOf(id);
1014             }
1015             return spaceSepIds;
1016         }
1017 
getRecipient(int recipientId)1018         private String getRecipient(int recipientId) {
1019             return id2Recipient.get(recipientId-1);
1020         }
1021 
1022         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)1023         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
1024                             String sortOrder) {
1025             if (uri.equals(TelephonyBackupAgent.ALL_THREADS_URI)) {
1026                 final int threadId = Integer.parseInt(selectionArgs[0]);
1027                 final String spaceSepIds = getSpaceSepIds(threadId);
1028                 List<ContentValues> table = new ArrayList<>();
1029                 ContentValues row = new ContentValues();
1030                 row.put(Telephony.Threads.RECIPIENT_IDS, spaceSepIds);
1031                 table.add(row);
1032                 return new FakeCursor(table, projection);
1033             } else if (uri.toString().startsWith(Telephony.Threads.CONTENT_URI.toString())) {
1034                 assertEquals(1, projection.length);
1035                 assertEquals(Telephony.Threads.ARCHIVED, projection[0]);
1036                 List<String> segments = uri.getPathSegments();
1037                 final int threadId = Integer.parseInt(segments.get(segments.size() - 2));
1038                 List<ContentValues> table = new ArrayList<>();
1039                 ContentValues row = new ContentValues();
1040                 row.put(Telephony.Threads.ARCHIVED, mIsThreadArchived.contains(threadId) ? 1 : 0);
1041                 table.add(row);
1042                 return new FakeCursor(table, projection);
1043             } else if (uri.toString().startsWith(
1044                     TelephonyBackupAgent.SINGLE_CANONICAL_ADDRESS_URI.toString())) {
1045                 final int recipientId = (int)ContentUris.parseId(uri);
1046                 final String recipient = getRecipient(recipientId);
1047                 List<ContentValues> table = new ArrayList<>();
1048                 ContentValues row = new ContentValues();
1049                 row.put(Telephony.CanonicalAddressesColumns.ADDRESS, recipient);
1050                 table.add(row);
1051 
1052                 return new FakeCursor(table,
1053                         projection != null
1054                                 ? projection
1055                                 : new String[] { Telephony.CanonicalAddressesColumns.ADDRESS });
1056             } else if (uri.toString().startsWith(
1057                     TelephonyBackupAgent.THREAD_ID_CONTENT_URI.toString())) {
1058                 List<String> recipients = uri.getQueryParameters("recipient");
1059 
1060                 final int threadId =
1061                         getOrCreateThreadId(recipients.toArray(new String[recipients.size()]));
1062                 List<ContentValues> table = new ArrayList<>();
1063                 ContentValues row = new ContentValues();
1064                 row.put(BaseColumns._ID, String.valueOf(threadId));
1065                 table.add(row);
1066                 return new FakeCursor(table, projection);
1067             } else {
1068                 fail("Unknown URI");
1069             }
1070             return null;
1071         }
1072 
1073         @Override
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)1074         public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
1075             assertEquals(uri, Telephony.Threads.CONTENT_URI);
1076             assertEquals(values.getAsInteger(Telephony.Threads.ARCHIVED).intValue(), 1);
1077             final int threadId = Integer.parseInt(selectionArgs[0]);
1078             mUpdateThreadsArchived.add(threadId);
1079             return 1;
1080         }
1081     }
1082 
1083     /**
1084      * general cursor for serving queries.
1085      */
1086     private static class FakeCursor extends MockCursor {
1087         String[] projection;
1088         List<ContentValues> rows;
1089         int nextRow = 0;
1090 
FakeCursor(List<ContentValues> rows, String[] projection)1091         public FakeCursor(List<ContentValues> rows, String[] projection) {
1092             this.projection = projection;
1093             this.rows = rows;
1094         }
1095 
setProjection(String[] projection)1096         public void setProjection(String[] projection) {
1097             this.projection = projection;
1098         }
1099 
1100         @Override
getColumnCount()1101         public int getColumnCount() {
1102             return projection.length;
1103         }
1104 
1105         @Override
getColumnName(int columnIndex)1106         public String getColumnName(int columnIndex) {
1107             return projection[columnIndex];
1108         }
1109 
1110         @Override
getString(int columnIndex)1111         public String getString(int columnIndex) {
1112             return rows.get(nextRow).getAsString(projection[columnIndex]);
1113         }
1114 
1115         @Override
getInt(int columnIndex)1116         public int getInt(int columnIndex) {
1117             return rows.get(nextRow).getAsInteger(projection[columnIndex]);
1118         }
1119 
1120         @Override
getLong(int columnIndex)1121         public long getLong(int columnIndex) {
1122             return rows.get(nextRow).getAsLong(projection[columnIndex]);
1123         }
1124 
1125         @Override
isAfterLast()1126         public boolean isAfterLast() {
1127             return nextRow >= getCount();
1128         }
1129 
1130         @Override
isLast()1131         public boolean isLast() {
1132             return nextRow == getCount() - 1;
1133         }
1134 
1135         @Override
moveToFirst()1136         public boolean moveToFirst() {
1137             nextRow = 0;
1138             return getCount() > 0;
1139         }
1140 
1141         @Override
moveToNext()1142         public boolean moveToNext() {
1143             return getCount() > ++nextRow;
1144         }
1145 
1146         @Override
getCount()1147         public int getCount() {
1148             return rows.size();
1149         }
1150 
1151         @Override
getColumnIndex(String columnName)1152         public int getColumnIndex(String columnName) {
1153             for (int i=0; i<projection.length; ++i) {
1154                 if (columnName.equals(projection[i])) {
1155                     return i;
1156                 }
1157             }
1158             return -1;
1159         }
1160 
1161         @Override
close()1162         public void close() {
1163         }
1164     }
1165 }
1166