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