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