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