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