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