xref: /aosp_15_r20/cts/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsPartTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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