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