Merge "Fix ENAMETOOLONG issue when set trash or pending to file"
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index c9c67b7..0d6494b 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -57,7 +57,6 @@
import android.system.OsConstants;
import android.text.TextUtils;
import android.text.format.DateUtils;
-import android.util.ArraySet;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -86,12 +85,16 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileUtils {
+ // Even though vfat allows 255 UCS-2 chars, we might eventually write to
+ // ext4 through a FUSE layer, so use that limit.
+ @VisibleForTesting
+ static final int MAX_FILENAME_BYTES = 255;
+
/**
* Drop-in replacement for {@link ParcelFileDescriptor#open(File, int)}
* which adds security features like {@link OsConstants#O_CLOEXEC} and
@@ -523,9 +526,8 @@
res.append('_');
}
}
- // Even though vfat allows 255 UCS-2 chars, we might eventually write to
- // ext4 through a FUSE layer, so use that limit.
- trimFilename(res, 255);
+
+ trimFilename(res, MAX_FILENAME_BYTES);
return res.toString();
}
@@ -1191,13 +1193,21 @@
if (!isForFuse && getAsBoolean(values, MediaColumns.IS_PENDING, false)) {
final long dateExpires = getAsLong(values, MediaColumns.DATE_EXPIRES,
(System.currentTimeMillis() + DEFAULT_DURATION_PENDING) / 1000);
- resolvedDisplayName = String.format(
+ final String combinedString = String.format(
Locale.US, ".%s-%d-%s", FileUtils.PREFIX_PENDING, dateExpires, displayName);
+ // trim the file name to avoid ENAMETOOLONG error
+ // after trim the file, if the user unpending the file,
+ // the file name is not the original one
+ resolvedDisplayName = trimFilename(combinedString, MAX_FILENAME_BYTES);
} else if (getAsBoolean(values, MediaColumns.IS_TRASHED, false)) {
final long dateExpires = getAsLong(values, MediaColumns.DATE_EXPIRES,
(System.currentTimeMillis() + DEFAULT_DURATION_TRASHED) / 1000);
- resolvedDisplayName = String.format(
+ final String combinedString = String.format(
Locale.US, ".%s-%d-%s", FileUtils.PREFIX_TRASHED, dateExpires, displayName);
+ // trim the file name to avoid ENAMETOOLONG error
+ // after trim the file, if the user untrashes the file,
+ // the file name is not the original one
+ resolvedDisplayName = trimFilename(combinedString, MAX_FILENAME_BYTES);
} else {
resolvedDisplayName = displayName;
}
diff --git a/tests/src/com/android/providers/media/MediaProviderTest.java b/tests/src/com/android/providers/media/MediaProviderTest.java
index 51d3628..655e7c6 100644
--- a/tests/src/com/android/providers/media/MediaProviderTest.java
+++ b/tests/src/com/android/providers/media/MediaProviderTest.java
@@ -21,6 +21,7 @@
import static com.android.providers.media.util.FileUtils.isDownload;
import static com.android.providers.media.util.FileUtils.isDownloadDir;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertArrayEquals;
@@ -63,6 +64,7 @@
import com.android.providers.media.MediaProvider.VolumeNotFoundException;
import com.android.providers.media.scan.MediaScannerTest.IsolatedContext;
import com.android.providers.media.util.FileUtils;
+import com.android.providers.media.util.FileUtilsTest;
import com.android.providers.media.util.SQLiteQueryBuilder;
import org.junit.AfterClass;
@@ -313,6 +315,59 @@
android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
+ @Test
+ public void testTrashLongFileNameItemHasTrimmedFileName() throws Exception {
+ testActionLongFileNameItemHasTrimmedFileName(MediaColumns.IS_TRASHED);
+ }
+
+ @Test
+ public void testPendingLongFileNameItemHasTrimmedFileName() throws Exception {
+ testActionLongFileNameItemHasTrimmedFileName(MediaColumns.IS_PENDING);
+ }
+
+ private void testActionLongFileNameItemHasTrimmedFileName(String columnKey) throws Exception {
+ // We might have old files lurking, so force a clean slate
+ final Context context = InstrumentationRegistry.getTargetContext();
+ sIsolatedContext = new IsolatedContext(context, "modern", /*asFuseThread*/ false);
+ sIsolatedResolver = sIsolatedContext.getContentResolver();
+ final String[] projection = new String[]{MediaColumns.DATA};
+ final File dir = Environment
+ .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+
+ // create extreme long file name
+ final String originalName = FileUtilsTest.createExtremeFileName("test" + System.nanoTime(),
+ ".jpg");
+
+ File file = stage(R.raw.lg_g4_iso_800_jpg, new File(dir, originalName));
+ final Uri uri = MediaStore.scanFile(sIsolatedResolver, file);
+ Log.v(TAG, "Scanned " + file + " as " + uri);
+
+ try (Cursor c = sIsolatedResolver.query(uri, projection, null, null)) {
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ final String data = c.getString(0);
+ final String result = FileUtils.extractDisplayName(data);
+ assertEquals(originalName, result);
+ }
+
+ final Bundle extras = new Bundle();
+ extras.putBoolean(MediaStore.QUERY_ARG_ALLOW_MOVEMENT, true);
+ final ContentValues values = new ContentValues();
+ values.put(columnKey, 1);
+ sIsolatedResolver.update(uri, values, extras);
+
+ try (Cursor c = sIsolatedResolver.query(uri, projection, null, null)) {
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ final String data = c.getString(0);
+ final String result = FileUtils.extractDisplayName(data);
+ assertThat(result.length()).isAtMost(FileUtilsTest.MAX_FILENAME_BYTES);
+ assertNotEquals(originalName, result);
+ }
+ }
+
/**
* We already have solid coverage of this logic in
* {@code CtsProviderTestCases}, but the coverage system currently doesn't
diff --git a/tests/src/com/android/providers/media/util/FileUtilsTest.java b/tests/src/com/android/providers/media/util/FileUtilsTest.java
index 5dd17ab..2e85421 100644
--- a/tests/src/com/android/providers/media/util/FileUtilsTest.java
+++ b/tests/src/com/android/providers/media/util/FileUtilsTest.java
@@ -83,6 +83,9 @@
@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
+ // Exposing here since it is also used by MediaProviderTest.java
+ public static final int MAX_FILENAME_BYTES = FileUtils.MAX_FILENAME_BYTES;
+
/**
* To help avoid flaky tests, give ourselves a unique nonce to be used for
* all filesystem paths, so that we don't risk conflicting with previous
@@ -712,6 +715,16 @@
}
@Test
+ public void testComputeDataFromValues_Trashed_trimFileName() throws Exception {
+ testComputeDataFromValues_withAction_trimFileName(MediaColumns.IS_TRASHED);
+ }
+
+ @Test
+ public void testComputeDataFromValues_Pending_trimFileName() throws Exception {
+ testComputeDataFromValues_withAction_trimFileName(MediaColumns.IS_PENDING);
+ }
+
+ @Test
public void testGetTopLevelNoMedia_CurrentDir() throws Exception {
File dirInDownload = getNewDirInDownload("testGetTopLevelNoMedia_CurrentDir");
File nomedia = new File(dirInDownload, ".nomedia");
@@ -798,4 +811,34 @@
assertTrue("Unexpected actual file " + actualFile, expectedSet.contains(actualFile));
}
}
+
+ public static String createExtremeFileName(String prefix, String extension) {
+ // create extreme long file name
+ final int prefixLength = prefix.length();
+ final int extensionLength = extension.length();
+ StringBuilder str = new StringBuilder(prefix);
+ for (int i = 0; i < (MAX_FILENAME_BYTES - prefixLength - extensionLength); i++) {
+ str.append(i % 10);
+ }
+ return str.append(extension).toString();
+ }
+
+ private void testComputeDataFromValues_withAction_trimFileName(String columnKey) {
+ final String originalName = createExtremeFileName("test", ".jpg");
+ final String volumePath = "/storage/emulated/0/";
+ final ContentValues values = new ContentValues();
+ values.put(columnKey, 1);
+ values.put(MediaColumns.RELATIVE_PATH, "DCIM/My Vacation/");
+ values.put(MediaColumns.DATE_EXPIRES, 1577836800L);
+ values.put(MediaColumns.DISPLAY_NAME, originalName);
+
+ FileUtils.computeDataFromValues(values, new File(volumePath), false /* isForFuse */);
+
+ final String data = values.getAsString(MediaColumns.DATA);
+ final String result = FileUtils.extractDisplayName(data);
+ // after adding the prefix .pending-timestamp or .trashed-timestamp,
+ // the largest length of the file name is MAX_FILENAME_BYTES 255
+ Truth.assertThat(result.length()).isAtMost(MAX_FILENAME_BYTES);
+ Truth.assertThat(result).isNotEqualTo(originalName);
+ }
}