| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.providers.media; |
| |
| import static android.provider.MediaStore.Downloads.isDownload; |
| import static android.provider.MediaStore.Downloads.isDownloadDir; |
| |
| import static com.android.providers.media.MediaProvider.ensureFileColumns; |
| import static com.android.providers.media.MediaProvider.extractPathOwnerPackageName; |
| import static com.android.providers.media.MediaProvider.maybeBalance; |
| import static com.android.providers.media.MediaProvider.recoverAbusiveGroupBy; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.database.Cursor; |
| import android.database.sqlite.SQLiteQueryBuilder; |
| import android.net.Uri; |
| import android.os.Environment; |
| import android.provider.MediaStore; |
| import android.provider.MediaStore.Images.ImageColumns; |
| import android.provider.MediaStore.MediaColumns; |
| import android.util.ArrayMap; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.providers.media.MediaProvider.VolumeArgumentException; |
| import com.android.providers.media.scan.MediaScannerTest.IsolatedContext; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.Arrays; |
| import java.util.regex.Pattern; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class MediaProviderTest { |
| static final String TAG = "MediaProviderTest"; |
| |
| @Test |
| public void testSchema() { |
| final Context context = InstrumentationRegistry.getTargetContext(); |
| final Context isolatedContext = new IsolatedContext(context, "modern"); |
| final ContentResolver isolatedResolver = isolatedContext.getContentResolver(); |
| |
| for (String path : new String[] { |
| "images/media", |
| "images/media/1", |
| "images/thumbnails", |
| "images/thumbnails/1", |
| |
| "audio/media", |
| "audio/media/1", |
| "audio/media/1/genres", |
| "audio/media/1/genres/1", |
| "audio/media/1/playlists", |
| "audio/media/1/playlists/1", |
| "audio/genres", |
| "audio/genres/1", |
| "audio/genres/1/members", |
| "audio/playlists", |
| "audio/playlists/1", |
| "audio/playlists/1/members", |
| "audio/playlists/1/members/1", |
| "audio/artists", |
| "audio/artists/1", |
| "audio/artists/1/albums", |
| "audio/albums", |
| "audio/albums/1", |
| "audio/albumart", |
| "audio/albumart/1", |
| |
| "video/media", |
| "video/media/1", |
| "video/thumbnails", |
| "video/thumbnails/1", |
| |
| "file", |
| "file/1", |
| |
| "downloads", |
| "downloads/1", |
| }) { |
| final Uri probe = MediaStore.AUTHORITY_URI.buildUpon() |
| .appendPath(MediaStore.VOLUME_EXTERNAL).appendEncodedPath(path).build(); |
| try (Cursor c = isolatedResolver.query(probe, null, null, null)) { |
| assertNotNull("probe", c); |
| } |
| } |
| } |
| |
| @Test |
| public void testComputeCommonPrefix_Single() { |
| assertEquals(Uri.parse("content://authority/1/2/3"), |
| MediaProvider.computeCommonPrefix(Arrays.asList( |
| Uri.parse("content://authority/1/2/3")))); |
| } |
| |
| @Test |
| public void testComputeCommonPrefix_Deeper() { |
| assertEquals(Uri.parse("content://authority/1/2/3"), |
| MediaProvider.computeCommonPrefix(Arrays.asList( |
| Uri.parse("content://authority/1/2/3/4"), |
| Uri.parse("content://authority/1/2/3/4/5"), |
| Uri.parse("content://authority/1/2/3")))); |
| } |
| |
| @Test |
| public void testComputeCommonPrefix_Siblings() { |
| assertEquals(Uri.parse("content://authority/1/2"), |
| MediaProvider.computeCommonPrefix(Arrays.asList( |
| Uri.parse("content://authority/1/2/3"), |
| Uri.parse("content://authority/1/2/99")))); |
| } |
| |
| @Test |
| public void testComputeCommonPrefix_Drastic() { |
| assertEquals(Uri.parse("content://authority"), |
| MediaProvider.computeCommonPrefix(Arrays.asList( |
| Uri.parse("content://authority/1/2/3"), |
| Uri.parse("content://authority/99/99/99")))); |
| } |
| |
| private static String getPathOwnerPackageName(String path) { |
| return extractPathOwnerPackageName(path); |
| } |
| |
| @Test |
| public void testPathOwnerPackageName_None() throws Exception { |
| assertEquals(null, getPathOwnerPackageName(null)); |
| assertEquals(null, getPathOwnerPackageName("/data/path")); |
| } |
| |
| @Test |
| public void testPathOwnerPackageName_Emulated() throws Exception { |
| assertEquals(null, getPathOwnerPackageName("/storage/emulated/0/DCIM/foo.jpg")); |
| assertEquals(null, getPathOwnerPackageName("/storage/emulated/0/Android/")); |
| assertEquals(null, getPathOwnerPackageName("/storage/emulated/0/Android/data/")); |
| |
| assertEquals("com.example", |
| getPathOwnerPackageName("/storage/emulated/0/Android/data/com.example/")); |
| assertEquals("com.example", |
| getPathOwnerPackageName("/storage/emulated/0/Android/data/com.example/foo.jpg")); |
| assertEquals("com.example", |
| getPathOwnerPackageName("/storage/emulated/0/Android/obb/com.example/foo.jpg")); |
| assertEquals("com.example", |
| getPathOwnerPackageName("/storage/emulated/0/Android/media/com.example/foo.jpg")); |
| assertEquals("com.example", |
| getPathOwnerPackageName("/storage/emulated/0/Android/sandbox/com.example/foo.jpg")); |
| } |
| |
| @Test |
| public void testPathOwnerPackageName_Portable() throws Exception { |
| assertEquals(null, getPathOwnerPackageName("/storage/0000-0000/DCIM/foo.jpg")); |
| |
| assertEquals("com.example", |
| getPathOwnerPackageName("/storage/0000-0000/Android/data/com.example/foo.jpg")); |
| } |
| |
| @Test |
| public void testBuildData_Simple() throws Exception { |
| final Uri uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertEndsWith("/Pictures/file.png", |
| buildFile(uri, null, null, "file", "image/png")); |
| assertEndsWith("/Pictures/file.png", |
| buildFile(uri, null, null, "file.png", "image/png")); |
| assertEndsWith("/Pictures/file.jpg.png", |
| buildFile(uri, null, null, "file.jpg", "image/png")); |
| } |
| |
| @Test |
| public void testBuildData_Primary() throws Exception { |
| final Uri uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertEndsWith("/DCIM/IMG_1024.JPG", |
| buildFile(uri, Environment.DIRECTORY_DCIM, null, "IMG_1024.JPG", "image/jpeg")); |
| } |
| |
| @Test |
| public void testBuildData_Secondary() throws Exception { |
| final Uri uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertEndsWith("/Pictures/Screenshots/foo.png", |
| buildFile(uri, null, Environment.DIRECTORY_SCREENSHOTS, "foo.png", "image/png")); |
| } |
| |
| @Test |
| public void testBuildData_InvalidNames() throws Exception { |
| final Uri uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertEndsWith("/Pictures/foo_bar.png", |
| buildFile(uri, null, null, "foo/bar", "image/png")); |
| assertEndsWith("/Pictures/_.hidden.png", |
| buildFile(uri, null, null, ".hidden", "image/png")); |
| } |
| |
| @Test |
| public void testBuildData_InvalidTypes() throws Exception { |
| for (String type : new String[] { |
| "audio/foo", "video/foo", "image/foo", "application/foo", "foo/foo" |
| }) { |
| if (!type.startsWith("audio/")) { |
| assertThrows(IllegalArgumentException.class, () -> { |
| buildFile(MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), |
| null, null, "foo", type); |
| }); |
| } |
| if (!type.startsWith("video/")) { |
| assertThrows(IllegalArgumentException.class, () -> { |
| buildFile(MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), |
| null, null, "foo", type); |
| }); |
| } |
| if (!type.startsWith("image/")) { |
| assertThrows(IllegalArgumentException.class, () -> { |
| buildFile(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), |
| null, null, "foo", type); |
| }); |
| } |
| } |
| } |
| |
| @Test |
| public void testBuildData_Charset() throws Exception { |
| final Uri uri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertEndsWith("/Pictures/foo__bar/bar__baz.png", |
| buildFile(uri, null, "foo\0\0bar", "bar::baz.png", "image/png")); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_Conflicting() throws Exception { |
| // Abusive group is fine |
| recoverAbusiveGroupBy(Pair.create("foo=bar GROUP BY foo", null)); |
| |
| // Official group is fine |
| recoverAbusiveGroupBy(Pair.create("foo=bar", "foo")); |
| |
| // Conflicting groups should yell |
| try { |
| recoverAbusiveGroupBy(Pair.create("foo=bar GROUP BY foo", "foo")); |
| fail("Expected IAE when conflicting groups defined"); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_Buckets() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "(media_type = 1 OR media_type = 3) AND bucket_display_name IS NOT NULL AND bucket_id IS NOT NULL AND _data NOT LIKE \"%/DCIM/%\" ) GROUP BY (bucket_id", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "((media_type = 1 OR media_type = 3) AND bucket_display_name IS NOT NULL AND bucket_id IS NOT NULL AND _data NOT LIKE \"%/DCIM/%\" )", |
| "(bucket_id)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_BucketsByPath() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "_data LIKE ? AND _data IS NOT NULL) GROUP BY (bucket_id", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "(_data LIKE ? AND _data IS NOT NULL)", |
| "(bucket_id)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_113651872() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "(LOWER(SUBSTR(_data, -4))=? OR LOWER(SUBSTR(_data, -5))=? OR LOWER(SUBSTR(_data, -4))=?) AND LOWER(SUBSTR(_data, 1, 65))!=?) GROUP BY (bucket_id),(bucket_display_name", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "((LOWER(SUBSTR(_data, -4))=? OR LOWER(SUBSTR(_data, -5))=? OR LOWER(SUBSTR(_data, -4))=?) AND LOWER(SUBSTR(_data, 1, 65))!=?)", |
| "(bucket_id),(bucket_display_name)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_113652519() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "1) GROUP BY 1,(2", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "(1)", |
| "1,(2)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_113652519_longer() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "mime_type IN ( ?, ?, ? ) AND 1) GROUP BY 1,(2", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "(mime_type IN ( ?, ?, ? ) AND 1)", |
| "1,(2)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_115340326() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "(1) GROUP BY bucket_id,(bucket_display_name)", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "(1)", |
| "bucket_id,(bucket_display_name)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testRecoverAbusiveGroupBy_116845885() throws Exception { |
| final Pair<String, String> input = Pair.create( |
| "(title like 'C360%' or title like 'getInstance%') group by ((datetaken+19800000)/86400000)", |
| null); |
| final Pair<String, String> expected = Pair.create( |
| "(title like 'C360%' or title like 'getInstance%')", |
| "((datetaken+19800000)/86400000)"); |
| assertEquals(expected, recoverAbusiveGroupBy(input)); |
| } |
| |
| @Test |
| public void testMaybeBalance() throws Exception { |
| assertEquals(null, maybeBalance(null)); |
| assertEquals("", maybeBalance("")); |
| |
| assertEquals("()", maybeBalance(")")); |
| assertEquals("()", maybeBalance("(")); |
| assertEquals("()", maybeBalance("()")); |
| |
| assertEquals("(1==1)", maybeBalance("1==1)")); |
| assertEquals("((foo)bar)baz", maybeBalance("foo)bar)baz")); |
| assertEquals("foo(bar(baz))", maybeBalance("foo(bar(baz")); |
| |
| assertEquals("IN '('", maybeBalance("IN '('")); |
| assertEquals("IN ('(')", maybeBalance("IN ('('")); |
| assertEquals("IN (\")\")", maybeBalance("IN (\")\"")); |
| assertEquals("IN ('\"(')", maybeBalance("IN ('\"('")); |
| } |
| |
| @Test |
| public void testGreylist() throws Exception { |
| assertFalse(isGreylistMatch( |
| "SELECT secret FROM other_table")); |
| |
| assertTrue(isGreylistMatch( |
| "case when case when (date_added >= 157680000 and date_added < 1892160000) then date_added * 1000 when (date_added >= 157680000000 and date_added < 1892160000000) then date_added when (date_added >= 157680000000000 and date_added < 1892160000000000) then date_added / 1000 else 0 end > case when (date_modified >= 157680000 and date_modified < 1892160000) then date_modified * 1000 when (date_modified >= 157680000000 and date_modified < 1892160000000) then date_modified when (date_modified >= 157680000000000 and date_modified < 1892160000000000) then date_modified / 1000 else 0 end then case when (date_added >= 157680000 and date_added < 1892160000) then date_added * 1000 when (date_added >= 157680000000 and date_added < 1892160000000) then date_added when (date_added >= 157680000000000 and date_added < 1892160000000000) then date_added / 1000 else 0 end else case when (date_modified >= 157680000 and date_modified < 1892160000) then date_modified * 1000 when (date_modified >= 157680000000 and date_modified < 1892160000000) then date_modified when (date_modified >= 157680000000000 and date_modified < 1892160000000000) then date_modified / 1000 else 0 end end as corrected_added_modified")); |
| assertTrue(isGreylistMatch( |
| "MAX(case when (datetaken >= 157680000 and datetaken < 1892160000) then datetaken * 1000 when (datetaken >= 157680000000 and datetaken < 1892160000000) then datetaken when (datetaken >= 157680000000000 and datetaken < 1892160000000000) then datetaken / 1000 else 0 end)")); |
| assertTrue(isGreylistMatch( |
| "0 as orientation")); |
| assertTrue(isGreylistMatch( |
| "\"content://media/internal/audio/media\"")); |
| } |
| |
| @Test |
| public void testGreylist_115845887() { |
| assertTrue(isGreylistMatch( |
| "MAX(*)")); |
| assertTrue(isGreylistMatch( |
| "MAX(_id)")); |
| |
| assertTrue(isGreylistMatch( |
| "sum(column_name)")); |
| assertFalse(isGreylistMatch( |
| "SUM(foo+bar)")); |
| |
| assertTrue(isGreylistMatch( |
| "count(column_name)")); |
| assertFalse(isGreylistMatch( |
| "count(other_table.column_name)")); |
| } |
| |
| @Test |
| public void testGreylist_116489751_116135586_116117120_116084561_116074030_116062802() { |
| assertTrue(isGreylistMatch( |
| "MAX(case when (date_added >= 157680000 and date_added < 1892160000) then date_added * 1000 when (date_added >= 157680000000 and date_added < 1892160000000) then date_added when (date_added >= 157680000000000 and date_added < 1892160000000000) then date_added / 1000 else 0 end)")); |
| } |
| |
| @Test |
| public void testGreylist_116699470() { |
| assertTrue(isGreylistMatch( |
| "MAX(case when (date_modified >= 157680000 and date_modified < 1892160000) then date_modified * 1000 when (date_modified >= 157680000000 and date_modified < 1892160000000) then date_modified when (date_modified >= 157680000000000 and date_modified < 1892160000000000) then date_modified / 1000 else 0 end)")); |
| } |
| |
| @Test |
| public void testGreylist_116531759() { |
| assertTrue(isGreylistMatch( |
| "count(*)")); |
| assertTrue(isGreylistMatch( |
| "COUNT(*)")); |
| assertFalse(isGreylistMatch( |
| "xCOUNT(*)")); |
| assertTrue(isGreylistMatch( |
| "count(*) AS image_count")); |
| assertTrue(isGreylistMatch( |
| "count(_id)")); |
| assertTrue(isGreylistMatch( |
| "count(_id) AS image_count")); |
| |
| assertTrue(isGreylistMatch( |
| "column_a AS column_b")); |
| assertFalse(isGreylistMatch( |
| "other_table.column_a AS column_b")); |
| } |
| |
| @Test |
| public void testGreylist_118475754() { |
| assertTrue(isGreylistMatch( |
| "count(*) pcount")); |
| assertTrue(isGreylistMatch( |
| "foo AS bar")); |
| assertTrue(isGreylistMatch( |
| "foo bar")); |
| assertTrue(isGreylistMatch( |
| "count(foo) AS bar")); |
| assertTrue(isGreylistMatch( |
| "count(foo) bar")); |
| } |
| |
| @Test |
| public void testGreylist_119522660() { |
| assertTrue(isGreylistMatch( |
| "CAST(_id AS TEXT) AS string_id")); |
| assertTrue(isGreylistMatch( |
| "cast(_id as text)")); |
| } |
| |
| @Test |
| public void testGreylist_126945991() { |
| assertTrue(isGreylistMatch( |
| "substr(_data, length(_data)-length(_display_name), 1) as filename_prevchar")); |
| } |
| |
| @Test |
| public void testGreylist_127900881() { |
| assertTrue(isGreylistMatch( |
| "*")); |
| } |
| |
| @Test |
| public void testGreylist_128389972() { |
| assertTrue(isGreylistMatch( |
| " count(bucket_id) images_count")); |
| } |
| |
| @Test |
| public void testGreylist_129746861() { |
| assertTrue(isGreylistMatch( |
| "case when (datetaken >= 157680000 and datetaken < 1892160000) then datetaken * 1000 when (datetaken >= 157680000000 and datetaken < 1892160000000) then datetaken when (datetaken >= 157680000000000 and datetaken < 1892160000000000) then datetaken / 1000 else 0 end")); |
| } |
| |
| @Test |
| public void testGreylist_114112523() { |
| assertTrue(isGreylistMatch( |
| "audio._id AS _id")); |
| } |
| |
| @Test |
| public void testComputeProjection() throws Exception { |
| final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); |
| final ArrayMap<String, String> map = new ArrayMap<>(); |
| map.put("external", "internal"); |
| builder.setProjectionMap(map); |
| builder.setProjectionAggregationAllowed(false); |
| builder.setStrict(true); |
| |
| assertArrayEquals( |
| new String[] { "internal" }, |
| builder.computeProjection(null)); |
| assertArrayEquals( |
| new String[] { "internal" }, |
| builder.computeProjection(new String[] { "external" })); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "internal" }); |
| }); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "MIN(internal)" }); |
| }); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "MIN(external)" }); |
| }); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "FOO(external)" }); |
| }); |
| } |
| |
| @Test |
| public void testComputeProjection_AggregationAllowed() throws Exception { |
| final SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); |
| final ArrayMap<String, String> map = new ArrayMap<>(); |
| map.put("external", "internal"); |
| builder.setProjectionMap(map); |
| builder.setProjectionAggregationAllowed(true); |
| builder.setStrict(true); |
| |
| assertArrayEquals( |
| new String[] { "internal" }, |
| builder.computeProjection(null)); |
| assertArrayEquals( |
| new String[] { "internal" }, |
| builder.computeProjection(new String[] { "external" })); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "internal" }); |
| }); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "MIN(internal)" }); |
| }); |
| assertArrayEquals( |
| new String[] { "MIN(internal)" }, |
| builder.computeProjection(new String[] { "MIN(external)" })); |
| assertThrows(IllegalArgumentException.class, () -> { |
| builder.computeProjection(new String[] { "FOO(external)" }); |
| }); |
| } |
| |
| @Test |
| public void testBindList() { |
| assertEquals("()", MediaProvider.bindList()); |
| assertEquals("( 'foo' )", MediaProvider.bindList("foo")); |
| assertEquals("( 'foo' , 'bar' )", MediaProvider.bindList("foo", "bar")); |
| assertEquals("( 'foo' , 'bar' , 'baz' )", MediaProvider.bindList("foo", "bar", "baz")); |
| assertEquals("( 'foo' , NULL , 42 )", MediaProvider.bindList("foo", null, 42)); |
| } |
| |
| @Test |
| public void testIsDownload() throws Exception { |
| assertTrue(isDownload("/storage/emulated/0/Download/colors.png")); |
| assertTrue(isDownload("/storage/emulated/0/Download/test.pdf")); |
| assertTrue(isDownload("/storage/emulated/0/Download/dir/foo.mp4")); |
| assertTrue(isDownload("/storage/0000-0000/Download/foo.txt")); |
| assertTrue(isDownload( |
| "/storage/emulated/0/Android/sandbox/com.example/Download/colors.png")); |
| assertTrue(isDownload( |
| "/storage/emulated/0/Android/sandbox/shared-com.uid.shared/Download/colors.png")); |
| assertTrue(isDownload( |
| "/storage/0000-0000/Android/sandbox/com.example/Download/colors.png")); |
| assertTrue(isDownload( |
| "/storage/0000-0000/Android/sandbox/shared-com.uid.shared/Download/colors.png")); |
| |
| |
| assertFalse(isDownload("/storage/emulated/0/Pictures/colors.png")); |
| assertFalse(isDownload("/storage/emulated/0/Pictures/Download/colors.png")); |
| assertFalse(isDownload("/storage/emulated/0/Android/data/com.example/Download/foo.txt")); |
| assertFalse(isDownload( |
| "/storage/emulated/0/Android/sandbox/com.example/dir/Download/foo.txt")); |
| assertFalse(isDownload("/storage/emulated/0/Download")); |
| assertFalse(isDownload("/storage/emulated/0/Android/sandbox/com.example/Download")); |
| assertFalse(isDownload( |
| "/storage/0000-0000/Android/sandbox/shared-com.uid.shared/Download")); |
| } |
| |
| @Test |
| public void testIsDownloadDir() throws Exception { |
| assertTrue(isDownloadDir("/storage/emulated/0/Download")); |
| assertTrue(isDownloadDir("/storage/emulated/0/Android/sandbox/com.example/Download")); |
| |
| assertFalse(isDownloadDir("/storage/emulated/0/Download/colors.png")); |
| assertFalse(isDownloadDir("/storage/emulated/0/Download/dir/")); |
| assertFalse(isDownloadDir( |
| "/storage/emulated/0/Android/sandbox/com.example/Download/dir/foo.txt")); |
| } |
| |
| @Test |
| public void testComputeDataValues_Grouped() throws Exception { |
| for (String data : new String[] { |
| "/storage/0000-0000/DCIM/Camera/IMG1024.JPG", |
| "/storage/0000-0000/DCIM/Camera/iMg1024.JpG", |
| "/storage/0000-0000/DCIM/Camera/IMG1024.CR2", |
| "/storage/0000-0000/DCIM/Camera/IMG1024.BURST001.JPG", |
| }) { |
| final ContentValues values = computeDataValues(data); |
| assertVolume(values, "0000-0000"); |
| assertBucket(values, "/storage/0000-0000/DCIM/Camera", "Camera"); |
| assertGroup(values, "IMG1024"); |
| assertDirectories(values, "DCIM/Camera/", "DCIM", "Camera"); |
| } |
| } |
| |
| @Test |
| public void testComputeDataValues_Extensions() throws Exception { |
| ContentValues values; |
| |
| values = computeDataValues("/storage/0000-0000/DCIM/Camera/IMG1024"); |
| assertVolume(values, "0000-0000"); |
| assertBucket(values, "/storage/0000-0000/DCIM/Camera", "Camera"); |
| assertGroup(values, null); |
| assertDirectories(values, "DCIM/Camera/", "DCIM", "Camera"); |
| |
| values = computeDataValues("/storage/0000-0000/DCIM/Camera/.foo"); |
| assertVolume(values, "0000-0000"); |
| assertBucket(values, "/storage/0000-0000/DCIM/Camera", "Camera"); |
| assertGroup(values, null); |
| assertDirectories(values, "DCIM/Camera/", "DCIM", "Camera"); |
| } |
| |
| @Test |
| public void testComputeDataValues_DirectoriesInvalid() throws Exception { |
| for (String data : new String[] { |
| "/storage/IMG1024.JPG", |
| "/data/media/IMG1024.JPG", |
| "IMG1024.JPG", |
| }) { |
| final ContentValues values = computeDataValues(data); |
| assertDirectories(values, null, null, null); |
| } |
| } |
| |
| @Test |
| public void testComputeDataValues_Directories() throws Exception { |
| ContentValues values; |
| |
| for (String top : new String[] { |
| "/storage/emulated/0", |
| "/storage/emulated/0/Android/sandbox/com.example", |
| }) { |
| values = computeDataValues(top + "/IMG1024.JPG"); |
| assertVolume(values, MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertBucket(values, top, null); |
| assertGroup(values, "IMG1024"); |
| assertDirectories(values, "/", null, null); |
| |
| values = computeDataValues(top + "/One/IMG1024.JPG"); |
| assertVolume(values, MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertBucket(values, top + "/One", "One"); |
| assertGroup(values, "IMG1024"); |
| assertDirectories(values, "One/", "One", null); |
| |
| values = computeDataValues(top + "/One/Two/IMG1024.JPG"); |
| assertVolume(values, MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertBucket(values, top + "/One/Two", "Two"); |
| assertGroup(values, "IMG1024"); |
| assertDirectories(values, "One/Two/", "One", "Two"); |
| |
| values = computeDataValues(top + "/One/Two/Three/IMG1024.JPG"); |
| assertVolume(values, MediaStore.VOLUME_EXTERNAL_PRIMARY); |
| assertBucket(values, top + "/One/Two/Three", "Three"); |
| assertGroup(values, "IMG1024"); |
| assertDirectories(values, "One/Two/Three/", "One", "Two"); |
| } |
| } |
| |
| private static ContentValues computeDataValues(String path) { |
| final ContentValues values = new ContentValues(); |
| values.put(MediaColumns.DATA, path); |
| MediaProvider.computeDataValues(values); |
| Log.v(TAG, "Computed values " + values); |
| return values; |
| } |
| |
| private static void assertBucket(ContentValues values, String bucketId, String bucketName) { |
| if (bucketId != null) { |
| assertEquals(bucketName, |
| values.getAsString(ImageColumns.BUCKET_DISPLAY_NAME)); |
| assertEquals(bucketId.toLowerCase().hashCode(), |
| (long) values.getAsLong(ImageColumns.BUCKET_ID)); |
| } else { |
| assertNull(values.get(ImageColumns.BUCKET_DISPLAY_NAME)); |
| assertNull(values.get(ImageColumns.BUCKET_ID)); |
| } |
| } |
| |
| private static void assertGroup(ContentValues values, String groupId) { |
| if (groupId != null) { |
| assertEquals(groupId.toLowerCase().hashCode(), |
| (long) values.getAsLong(ImageColumns.GROUP_ID)); |
| } else { |
| assertNull(values.get(ImageColumns.GROUP_ID)); |
| } |
| } |
| |
| private static void assertVolume(ContentValues values, String volumeName) { |
| assertEquals(volumeName, values.getAsString(ImageColumns.VOLUME_NAME)); |
| } |
| |
| private static void assertDirectories(ContentValues values, String relativePath, |
| String primaryDir, String secondaryDir) { |
| assertEquals(relativePath, values.get(ImageColumns.RELATIVE_PATH)); |
| assertEquals(primaryDir, values.get(ImageColumns.PRIMARY_DIRECTORY)); |
| assertEquals(secondaryDir, values.get(ImageColumns.SECONDARY_DIRECTORY)); |
| } |
| |
| private static boolean isGreylistMatch(String raw) { |
| for (Pattern p : MediaProvider.sGreylist) { |
| if (p.matcher(raw).matches()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static String buildFile(Uri uri, String primaryDir, String secondaryDir, |
| String displayName, String mimeType) { |
| final ContentValues values = new ContentValues(); |
| if (primaryDir != null) { |
| values.put(MediaColumns.PRIMARY_DIRECTORY, primaryDir); |
| } |
| if (secondaryDir != null) { |
| values.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDir); |
| } |
| values.put(MediaColumns.DISPLAY_NAME, displayName); |
| values.put(MediaColumns.MIME_TYPE, mimeType); |
| try { |
| ensureFileColumns(uri, values); |
| } catch (VolumeArgumentException e) { |
| throw e.rethrowAsIllegalArgumentException(); |
| } |
| return values.getAsString(MediaColumns.DATA); |
| } |
| |
| private static void assertEndsWith(String expected, String actual) { |
| if (!actual.endsWith(expected)) { |
| fail("Expected ends with " + expected + " but found " + actual); |
| } |
| } |
| |
| private static <T extends Exception> void assertThrows(Class<T> clazz, Runnable r) { |
| try { |
| r.run(); |
| fail("Expected " + clazz + " to be thrown"); |
| } catch (Exception e) { |
| if (!clazz.isAssignableFrom(e.getClass())) { |
| throw e; |
| } |
| } |
| } |
| } |