1 /* 2 * Copyright (C) 2019 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 android.telephonyprovider.cts; 18 19 import static android.telephony.cts.util.DefaultSmsAppHelper.assumeTelephony; 20 import static android.telephony.cts.util.DefaultSmsAppHelper.assumeMessaging; 21 22 import static androidx.test.InstrumentationRegistry.getInstrumentation; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static java.nio.charset.StandardCharsets.UTF_8; 27 28 import android.content.ContentResolver; 29 import android.content.ContentUris; 30 import android.content.ContentValues; 31 import android.database.Cursor; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.provider.Telephony; 35 import android.telephony.cts.util.DefaultSmsAppHelper; 36 37 import com.android.compatibility.common.util.ApiTest; 38 39 import com.android.compatibility.common.util.ApiTest; 40 41 import org.junit.AfterClass; 42 import org.junit.Before; 43 import org.junit.BeforeClass; 44 import org.junit.Test; 45 46 import java.io.OutputStream; 47 48 import javax.annotation.Nullable; 49 50 public class MmsPartTest { 51 52 private static final String MMS_SUBJECT_ONE = "MMS Subject CTS One"; 53 private static final String MMS_SUBJECT_PART = "Part cleanup test"; 54 private static final String MMS_BODY = "MMS body CTS"; 55 private static final String MMS_BODY_UPDATE = "MMS body CTS Update"; 56 private static final String TEXT_PLAIN = "text/plain"; 57 public static final String IMAGE_JPEG = "image/jpeg"; 58 private static final String SRC_NAME = String.format("text.%06d.txt", 0); 59 static final String PART_FILE_COUNT = "part_file_count"; 60 static final String PART_TABLE_ENTRY_COUNT = "part_table_entry_count"; 61 static final String DELETED_COUNT = "deleted_count"; 62 static final String METHOD_GARBAGE_COLLECT = "garbage_collect"; 63 64 /** 65 * Parts must be inserted in relation to a message, this message ID is used for inserting a part 66 * when the message ID is not important in relation to the current test. 67 */ 68 private static final String TEST_MESSAGE_ID = "100"; 69 private ContentResolver mContentResolver; 70 71 @BeforeClass ensureDefaultSmsApp()72 public static void ensureDefaultSmsApp() { 73 DefaultSmsAppHelper.ensureDefaultSmsApp(); 74 } 75 76 @AfterClass cleanup()77 public static void cleanup() { 78 ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver(); 79 contentResolver.delete(Telephony.Mms.Part.CONTENT_URI, null, null); 80 contentResolver 81 .call(Telephony.MmsSms.CONTENT_URI, "garbage_collect", "delete", null); 82 } 83 84 @Before setUp()85 public void setUp() { 86 cleanup(); 87 } 88 89 @Before setupTestEnvironment()90 public void setupTestEnvironment() { 91 assumeTelephony(); 92 assumeMessaging(); 93 cleanup(); 94 mContentResolver = getInstrumentation().getContext().getContentResolver(); 95 } 96 97 @Test testMmsPartInsert_cannotInsertPartWithDataColumn()98 public void testMmsPartInsert_cannotInsertPartWithDataColumn() { 99 ContentValues values = new ContentValues(); 100 values.put(Telephony.Mms.Part._DATA, "/dev/urandom"); 101 values.put(Telephony.Mms.Part.NAME, "testMmsPartInsert_cannotInsertPartWithDataColumn"); 102 103 Uri uri = insertTestMmsPartWithValues(values); 104 assertThat(uri).isNull(); 105 } 106 107 @Test testMmsPartInsert_canInsertPartWithoutDataColumn()108 public void testMmsPartInsert_canInsertPartWithoutDataColumn() throws Exception { 109 String name = "testMmsInsert_canInsertPartWithoutDataColumn"; 110 111 Uri mmsUri = insertIntoMmsTable(MMS_SUBJECT_ONE); 112 assertThat(mmsUri).isNotNull(); 113 final long mmsId = ContentUris.parseId(mmsUri); 114 115 //Creating part uri using mmsId. 116 final Uri partUri = Telephony.Mms.Part.getPartUriForMessage(String.valueOf(mmsId)); 117 Uri insertPartUri = insertIntoMmsPartTable(mmsUri, partUri, mmsId, MMS_BODY, 118 name, TEXT_PLAIN); 119 assertThatMmsPartInsertSucceeded(insertPartUri, name, MMS_BODY); 120 } 121 122 @Test testMmsPart_deletedPartIdsAreNotReused()123 public void testMmsPart_deletedPartIdsAreNotReused() throws Exception { 124 long id1 = insertAndVerifyMmsPartReturningId("testMmsPart_deletedPartIdsAreNotReused_1"); 125 126 deletePartById(id1); 127 128 long id2 = insertAndVerifyMmsPartReturningId("testMmsPart_deletedPartIdsAreNotReused_2"); 129 130 assertThat(id2).isGreaterThan(id1); 131 } 132 133 @Test testMmsPart_garbageCollectWithOnlyOneValidPart()134 public void testMmsPart_garbageCollectWithOnlyOneValidPart() throws Exception { 135 long id1 = insertAndVerifyMmsPart(0); 136 assertThat(id1).isGreaterThan(0); 137 138 Bundle result = 139 mContentResolver 140 .call(Telephony.MmsSms.CONTENT_URI, METHOD_GARBAGE_COLLECT, 141 null, null); 142 143 assertThat(result.getInt(PART_FILE_COUNT)).isEqualTo(1); 144 assertThat(result.getInt(PART_TABLE_ENTRY_COUNT)).isEqualTo(1); 145 assertThat(result.getInt(DELETED_COUNT)).isEqualTo(0); 146 } 147 148 @Test testMmsPart_verifyLargeNumberOfParts()149 public void testMmsPart_verifyLargeNumberOfParts() throws Exception { 150 int partCount = 500; 151 for (int i = 0; i < partCount; i++) { 152 long id1 = insertAndVerifyMmsPart(i); 153 assertThat(id1).isGreaterThan(0); 154 } 155 156 Bundle result = 157 mContentResolver 158 .call(Telephony.MmsSms.CONTENT_URI, METHOD_GARBAGE_COLLECT, 159 null, null); 160 161 assertThat(result.getInt(PART_FILE_COUNT)).isEqualTo(partCount); 162 assertThat(result.getInt(PART_TABLE_ENTRY_COUNT)).isEqualTo(partCount); 163 assertThat(result.getInt(DELETED_COUNT)).isEqualTo(0); 164 } 165 166 @Test testMmsPart_garbageCollectWithOnlyValidParts()167 public void testMmsPart_garbageCollectWithOnlyValidParts() throws Exception { 168 int partCount = 10; 169 for (int i = 0; i < partCount; i++) { 170 long id1 = insertAndVerifyMmsPart(i); 171 assertThat(id1).isGreaterThan(0); 172 } 173 174 Bundle result = 175 mContentResolver 176 .call(Telephony.MmsSms.CONTENT_URI, METHOD_GARBAGE_COLLECT, 177 "delete", null); 178 179 assertThat(result.getInt(PART_FILE_COUNT)).isEqualTo(partCount); 180 assertThat(result.getInt(PART_TABLE_ENTRY_COUNT)).isEqualTo(partCount); 181 assertThat(result.getInt(DELETED_COUNT)).isEqualTo(0); 182 } 183 getMessagePartUri(long msgId)184 private static Uri getMessagePartUri(long msgId) { 185 return Uri.parse("content://mms/" + msgId + "/part"); 186 } 187 188 @Test testMmsPartUpdate()189 public void testMmsPartUpdate() throws Exception { 190 //Inserting data to MMS table. 191 Uri mmsUri = insertIntoMmsTable(MMS_SUBJECT_ONE); 192 assertThat(mmsUri).isNotNull(); 193 final long mmsId = ContentUris.parseId(mmsUri); 194 195 //Creating part uri using mmsId. 196 final Uri partUri = Telephony.Mms.Part.getPartUriForMessage(String.valueOf(mmsId)); 197 198 //Inserting data to MmsPart table with mapping with mms uri. 199 Uri insertPartUri = insertIntoMmsPartTable(mmsUri, partUri, mmsId, MMS_BODY, SRC_NAME, 200 TEXT_PLAIN); 201 assertThatMmsPartInsertSucceeded(insertPartUri, SRC_NAME, MMS_BODY); 202 203 final ContentValues updateValues = new ContentValues(); 204 updateValues.put(Telephony.Mms.Part.TEXT, MMS_BODY_UPDATE); 205 206 // Updating part table. 207 int cursorUpdate = mContentResolver.update(partUri, updateValues, null, null); 208 assertThat(cursorUpdate).isEqualTo(1); 209 assertThatMmsPartInsertSucceeded(insertPartUri, SRC_NAME, MMS_BODY_UPDATE); 210 211 } 212 213 /** 214 * Verifies uri path outside the directory of mms parts is not allowed. 215 */ 216 @Test 217 @ApiTest(apis = "com.android.providers.telephony.MmsProvider#update") testMmsPartUpdate_invalidUri()218 public void testMmsPartUpdate_invalidUri() { 219 ContentValues cv = new ContentValues(); 220 Uri uri = Uri.parse("content://mms/resetFilePerm/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F.." 221 + "%2F..%2F..%2F..%2F..%2Fdata%2Fuser_de%2F0%2Fcom.android.providers.telephony" 222 + "%2Fdatabases"); 223 int cursorUpdate = mContentResolver.update(uri, cv, null, null); 224 assertThat(cursorUpdate).isEqualTo(0); 225 } 226 227 @Test testMmsPartDelete_canDeleteById()228 public void testMmsPartDelete_canDeleteById() throws Exception { 229 Uri mmsUri = insertIntoMmsTable(MMS_SUBJECT_ONE); 230 assertThat(mmsUri).isNotNull(); 231 final long mmsId = ContentUris.parseId(mmsUri); 232 final Uri partUri = Telephony.Mms.Part.getPartUriForMessage(String.valueOf(mmsId)); 233 234 Uri insertPartUri = insertIntoMmsPartTable(mmsUri, partUri, mmsId, MMS_BODY, SRC_NAME, 235 TEXT_PLAIN); 236 assertThat(insertPartUri).isNotNull(); 237 238 int deletedRows = mContentResolver.delete(partUri, null, null); 239 240 assertThat(deletedRows).isEqualTo(1); 241 242 } 243 insertAndVerifyMmsPartReturningId(String name)244 private long insertAndVerifyMmsPartReturningId(String name) throws Exception { 245 Uri mmsUri = insertIntoMmsTable(MMS_SUBJECT_ONE); 246 assertThat(mmsUri).isNotNull(); 247 final long mmsId = ContentUris.parseId(mmsUri); 248 249 //Creating part uri using mmsId. 250 final Uri partUri = Telephony.Mms.Part.getPartUriForMessage(String.valueOf(mmsId)); 251 Uri insertPartUri = insertIntoMmsPartTable(mmsUri, partUri, mmsId, MMS_BODY, name, 252 TEXT_PLAIN); 253 254 assertThatMmsPartInsertSucceeded(insertPartUri, name, MMS_BODY); 255 return Long.parseLong(insertPartUri.getLastPathSegment()); 256 } 257 insertAndVerifyMmsPart(int fileCounter)258 private long insertAndVerifyMmsPart(int fileCounter) throws Exception { 259 Uri mmsUri = insertIntoMmsTableNotText(MMS_SUBJECT_PART); 260 assertThat(mmsUri).isNotNull(); 261 final long mmsId = ContentUris.parseId(mmsUri); 262 263 // Creating part uri using mmsId. 264 String filename = "file" + fileCounter; 265 final Uri partUri = getPartUriForMessage(String.valueOf(mmsId)); 266 Uri insertPartUri = insertIntoMmsPartTable(partUri, mmsId, MMS_BODY, filename, IMAGE_JPEG); 267 268 assertThatMmsPartInsertWithContentTypeSucceeded(insertPartUri, filename, IMAGE_JPEG); 269 return Long.parseLong(insertPartUri.getLastPathSegment()); 270 } 271 getPartUriForMessage(String messageId)272 private static Uri getPartUriForMessage(String messageId) { 273 return Telephony.Mms.CONTENT_URI 274 .buildUpon() 275 .appendPath(String.valueOf(messageId)) 276 .appendPath("part") 277 .build(); 278 } 279 insertAndVerifyMmsPart(String name)280 private long insertAndVerifyMmsPart(String name) throws Exception { 281 Uri mmsUri = insertIntoMmsTableNotText(MMS_SUBJECT_PART); 282 assertThat(mmsUri).isNotNull(); 283 final long mmsId = ContentUris.parseId(mmsUri); 284 285 //Creating part uri using mmsId. 286 final Uri partUri = Telephony.Mms.Part.getPartUriForMessage(String.valueOf(mmsId)); 287 Uri insertPartUri = insertIntoMmsPartTable(mmsUri, partUri, mmsId, MMS_BODY, name, 288 IMAGE_JPEG); 289 290 assertThatMmsPartInsertWithContentTypeSucceeded(insertPartUri, name, IMAGE_JPEG); 291 return Long.parseLong(insertPartUri.getLastPathSegment()); 292 } 293 deletePartById(long partId)294 private void deletePartById(long partId) { 295 Uri uri = Uri.withAppendedPath(Telephony.Mms.Part.CONTENT_URI, Long.toString(partId)); 296 int deletedRows = mContentResolver.delete(uri, null, null); 297 assertThat(deletedRows).isEqualTo(1); 298 } 299 insertTestMmsPartWithValues(ContentValues values)300 private Uri insertTestMmsPartWithValues(ContentValues values) { 301 Uri insertUri = Telephony.Mms.Part.getPartUriForMessage(TEST_MESSAGE_ID); 302 303 Uri uri = mContentResolver.insert(insertUri, values); 304 return uri; 305 } 306 assertThatMmsPartInsertSucceeded(@ullable Uri uriReturnedFromInsert, String nameOfAttemptedInsert, String textBody)307 private void assertThatMmsPartInsertSucceeded(@Nullable Uri uriReturnedFromInsert, 308 String nameOfAttemptedInsert, String textBody) { 309 assertThat(uriReturnedFromInsert).isNotNull(); 310 311 Cursor cursor = mContentResolver.query(uriReturnedFromInsert, null, null, null); 312 assertThat(cursor.getCount()).isEqualTo(1); 313 314 cursor.moveToNext(); 315 String actualName = cursor.getString(cursor.getColumnIndex(Telephony.Mms.Part.NAME)); 316 assertThat(actualName).isEqualTo(nameOfAttemptedInsert); 317 assertThat(cursor.getString(cursor.getColumnIndex(Telephony.Mms.Part.TEXT))).isEqualTo( 318 textBody); 319 } 320 assertThatMmsPartInsertWithContentTypeSucceeded( @ullable Uri uriReturnedFromInsert, String nameOfAttemptedInsert, String contentType)321 private void assertThatMmsPartInsertWithContentTypeSucceeded( 322 @Nullable Uri uriReturnedFromInsert, String nameOfAttemptedInsert, String contentType) { 323 assertThat(uriReturnedFromInsert).isNotNull(); 324 325 Cursor cursor = mContentResolver.query(uriReturnedFromInsert, null, null, null); 326 assertThat(cursor.getCount()).isEqualTo(1); 327 328 cursor.moveToNext(); 329 String actualName = cursor.getString(cursor.getColumnIndex(Telephony.Mms.Part.NAME)); 330 assertThat(actualName).isEqualTo(nameOfAttemptedInsert); 331 assertThat(cursor.getString(cursor.getColumnIndex(Telephony.Mms.Part.CONTENT_TYPE))) 332 .isEqualTo(contentType); 333 } 334 insertIntoMmsTable(String subject)335 private Uri insertIntoMmsTable(String subject) { 336 final ContentValues mmsValues = new ContentValues(); 337 mmsValues.put(Telephony.Mms.TEXT_ONLY, 1); 338 mmsValues.put(Telephony.Mms.MESSAGE_TYPE, 128); 339 mmsValues.put(Telephony.Mms.SUBJECT, subject); 340 final Uri mmsUri = mContentResolver.insert(Telephony.Mms.CONTENT_URI, mmsValues); 341 return mmsUri; 342 } 343 insertIntoMmsTableNotText(String subject)344 private Uri insertIntoMmsTableNotText(String subject) { 345 final ContentValues mmsValues = new ContentValues(); 346 mmsValues.put(Telephony.Mms.TEXT_ONLY, 0); 347 mmsValues.put(Telephony.Mms.MESSAGE_TYPE, 128); 348 mmsValues.put(Telephony.Mms.SUBJECT, subject); 349 final Uri mmsUri = mContentResolver.insert(Telephony.Mms.CONTENT_URI, mmsValues); 350 return mmsUri; 351 } 352 insertIntoMmsPartTable( Uri partUri, long mmsId, String body, String name, String contentType)353 private Uri insertIntoMmsPartTable( 354 Uri partUri, long mmsId, String body, String name, String contentType) 355 throws Exception { 356 // Insert body part. 357 final ContentValues values = new ContentValues(); 358 values.put(Telephony.Mms.Part.MSG_ID, mmsId); 359 values.put(Telephony.Mms.Part.NAME, name); 360 values.put(Telephony.Mms.Part.SEQ, 0); 361 values.put(Telephony.Mms.Part.CONTENT_TYPE, contentType); 362 values.put(Telephony.Mms.Part.CONTENT_ID, "<" + name + ">"); 363 values.put(Telephony.Mms.Part.CONTENT_LOCATION, name); 364 values.put(Telephony.Mms.Part.CHARSET, 111); 365 values.put(Telephony.Mms.Part.TEXT, body); 366 Uri insertPartUri = mContentResolver.insert(partUri, values); 367 368 if (!TEXT_PLAIN.equals(contentType)) { 369 writePartFile(insertPartUri); 370 } 371 return insertPartUri; 372 } 373 insertIntoMmsPartTable(Uri mmsUri, Uri partUri, long mmsId, String body, String name, String contentType)374 private Uri insertIntoMmsPartTable(Uri mmsUri, Uri partUri, long mmsId, String body, 375 String name, String contentType) throws Exception { 376 // Insert body part. 377 final ContentValues values = new ContentValues(); 378 values.put(Telephony.Mms.Part.MSG_ID, mmsId); 379 values.put(Telephony.Mms.Part.NAME, name); 380 values.put(Telephony.Mms.Part.SEQ, 0); 381 values.put(Telephony.Mms.Part.CONTENT_TYPE, contentType); 382 values.put(Telephony.Mms.Part.CONTENT_ID, "<" + SRC_NAME + ">"); 383 values.put(Telephony.Mms.Part.CONTENT_LOCATION, SRC_NAME); 384 values.put(Telephony.Mms.Part.CHARSET, 111); 385 values.put(Telephony.Mms.Part.TEXT, body); 386 Uri insertPartUri = mContentResolver.insert(partUri, values); 387 388 if (!TEXT_PLAIN.equals(contentType)) { 389 writePartFile(insertPartUri); 390 } 391 return insertPartUri; 392 } 393 writePartFile(Uri uri)394 private void writePartFile(Uri uri) throws Exception { 395 // uri can look like: 396 // content://mms/part/98 397 try (OutputStream os = mContentResolver.openOutputStream(uri)) { 398 if (os == null) { 399 throw new Exception("Failed to create output stream on " + uri); 400 } 401 String test = "This is a test"; 402 os.write(test.getBytes(UTF_8), 0, test.length()); 403 } 404 } 405 } 406