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