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