Move extension checking code into FileUtils.

Move code for checking file extensions and MIME types from
ExternalStorageProvider into android.os.FileUtils, so it can be used by
other clients (e.g. DownloadsProvider).

BUG=20157955

Change-Id: Ib16a16af723c21fb8d2912c8917dfd68653ea6fa
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index b302f95..931cd3e 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -16,11 +16,13 @@
 
 package android.os;
 
+import android.provider.DocumentsContract.Document;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
+import android.webkit.MimeTypeMap;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
@@ -34,6 +36,7 @@
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Objects;
 import java.util.regex.Pattern;
 import java.util.zip.CRC32;
 import java.util.zip.CheckedInputStream;
@@ -533,4 +536,76 @@
         }
         return null;
     }
+
+    /**
+     * Generates a unique file name under the given parent directory. If the display name doesn't
+     * have an extension that matches the requested MIME type, the default extension for that MIME
+     * type is appended. If a file already exists, the name is appended with a numerical value to
+     * make it unique.
+     *
+     * For example, the display name 'example' with 'text/plain' MIME might produce
+     * 'example.txt' or 'example (1).txt', etc.
+     *
+     * @throws FileNotFoundException
+     */
+    public static File buildUniqueFile(File parent, String mimeType, String displayName)
+            throws FileNotFoundException {
+        String name;
+        String ext;
+
+        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+            name = displayName;
+            ext = null;
+        } else {
+            String mimeTypeFromExt;
+
+            // Extract requested extension from display name
+            final int lastDot = displayName.lastIndexOf('.');
+            if (lastDot >= 0) {
+                name = displayName.substring(0, lastDot);
+                ext = displayName.substring(lastDot + 1);
+                mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+                        ext.toLowerCase());
+            } else {
+                name = displayName;
+                ext = null;
+                mimeTypeFromExt = null;
+            }
+
+            if (mimeTypeFromExt == null) {
+                mimeTypeFromExt = "application/octet-stream";
+            }
+
+            final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(
+                    mimeType);
+            if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
+                // Extension maps back to requested MIME type; allow it
+            } else {
+                // No match; insist that create file matches requested MIME
+                name = displayName;
+                ext = extFromMimeType;
+            }
+        }
+
+        File file = buildFile(parent, name, ext);
+
+        // If conflicting file, try adding counter suffix
+        int n = 0;
+        while (file.exists()) {
+            if (n++ >= 32) {
+                throw new FileNotFoundException("Failed to create unique file");
+            }
+            file = buildFile(parent, name + " (" + n + ")", ext);
+        }
+
+        return file;
+    }
+
+    private static File buildFile(File parent, String name, String ext) {
+        if (TextUtils.isEmpty(ext)) {
+            return new File(parent, name);
+        } else {
+            return new File(parent, name + "." + ext);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 5c9e813..ee9e2e4 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -21,6 +21,7 @@
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 
 import android.content.Context;
+import android.provider.DocumentsContract.Document;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
@@ -43,6 +44,8 @@
     private File mDir;
     private File mTestFile;
     private File mCopyFile;
+    private File mTarget;
+
 
     @Override
     protected void setUp() throws Exception {
@@ -50,11 +53,15 @@
         mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
         mTestFile = new File(mDir, "test.file");
         mCopyFile = new File(mDir, "copy.file");
+
+        mTarget = getContext().getFilesDir();
+        FileUtils.deleteContents(mTarget);
     }
 
     @Override
     protected void tearDown() throws Exception {
         IoUtils.deleteContents(mDir);
+        FileUtils.deleteContents(mTarget);
     }
 
     // TODO: test setPermissions(), getPermissions()
@@ -225,6 +232,63 @@
         assertEquals("foo_bar__baz", FileUtils.buildValidFatFilename("foo?bar**baz"));
     }
 
+    public void testBuildUniqueFile_normal() throws Exception {
+        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
+        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
+        assertNameEquals("test.jpeg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpeg"));
+        assertNameEquals("TEst.JPeg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "TEst.JPeg"));
+        assertNameEquals("test.png.jpg",
+                FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.png.jpg"));
+        assertNameEquals("test.png.jpg",
+                FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.png"));
+
+        assertNameEquals("test.flac", FileUtils.buildUniqueFile(mTarget, "audio/flac", "test"));
+        assertNameEquals("test.flac", FileUtils.buildUniqueFile(mTarget, "audio/flac", "test.flac"));
+        assertNameEquals("test.flac",
+                FileUtils.buildUniqueFile(mTarget, "application/x-flac", "test"));
+        assertNameEquals("test.flac",
+                FileUtils.buildUniqueFile(mTarget, "application/x-flac", "test.flac"));
+    }
+
+    public void testBuildUniqueFile_unknown() throws Exception {
+        assertNameEquals("test",
+                FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
+        assertNameEquals("test.jpg",
+                FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test.jpg"));
+        assertNameEquals(".test",
+                FileUtils.buildUniqueFile(mTarget, "application/octet-stream", ".test"));
+
+        assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, "lolz/lolz", "test"));
+        assertNameEquals("test.lolz", FileUtils.buildUniqueFile(mTarget, "lolz/lolz", "test.lolz"));
+    }
+
+    public void testBuildUniqueFile_dir() throws Exception {
+        assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
+        new File(mTarget, "test").mkdir();
+        assertNameEquals("test (1)",
+                FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
+
+        assertNameEquals("test.jpg",
+                FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg"));
+        new File(mTarget, "test.jpg").mkdir();
+        assertNameEquals("test.jpg (1)",
+                FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg"));
+    }
+
+    public void testBuildUniqueFile_increment() throws Exception {
+        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
+        new File(mTarget, "test.jpg").createNewFile();
+        assertNameEquals("test (1).jpg",
+                FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
+        new File(mTarget, "test (1).jpg").createNewFile();
+        assertNameEquals("test (2).jpg",
+                FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
+    }
+
+    private static void assertNameEquals(String expected, File actual) {
+        assertEquals(expected, actual.getName());
+    }
+
     private void touch(String name, long age) throws Exception {
         final File file = new File(mDir, name);
         file.createNewFile();
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 2f97809..67d8ab6 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -11,3 +11,5 @@
 LOCAL_CERTIFICATE := platform
 
 include $(BUILD_PACKAGE)
+
+include $(LOCAL_PATH)/tests/Android.mk
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index aff57bf..73a723d 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -45,7 +45,6 @@
 import android.webkit.MimeTypeMap;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.File;
@@ -55,7 +54,6 @@
 import java.io.PrintWriter;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Objects;
 
 public class ExternalStorageProvider extends DocumentsProvider {
     private static final String TAG = "ExternalStorage";
@@ -327,7 +325,7 @@
             throw new IllegalArgumentException("Parent document isn't a directory");
         }
 
-        final File file = buildUniqueFile(parent, mimeType, displayName);
+        final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
         if (Document.MIME_TYPE_DIR.equals(mimeType)) {
             if (!file.mkdir()) {
                 throw new IllegalStateException("Failed to mkdir " + file);
@@ -345,68 +343,6 @@
         return getDocIdForFile(file);
     }
 
-    private static File buildFile(File parent, String name, String ext) {
-        if (TextUtils.isEmpty(ext)) {
-            return new File(parent, name);
-        } else {
-            return new File(parent, name + "." + ext);
-        }
-    }
-
-    @VisibleForTesting
-    public static File buildUniqueFile(File parent, String mimeType, String displayName)
-            throws FileNotFoundException {
-        String name;
-        String ext;
-
-        if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-            name = displayName;
-            ext = null;
-        } else {
-            String mimeTypeFromExt;
-
-            // Extract requested extension from display name
-            final int lastDot = displayName.lastIndexOf('.');
-            if (lastDot >= 0) {
-                name = displayName.substring(0, lastDot);
-                ext = displayName.substring(lastDot + 1);
-                mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
-                        ext.toLowerCase());
-            } else {
-                name = displayName;
-                ext = null;
-                mimeTypeFromExt = null;
-            }
-
-            if (mimeTypeFromExt == null) {
-                mimeTypeFromExt = "application/octet-stream";
-            }
-
-            final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(
-                    mimeType);
-            if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
-                // Extension maps back to requested MIME type; allow it
-            } else {
-                // No match; insist that create file matches requested MIME
-                name = displayName;
-                ext = extFromMimeType;
-            }
-        }
-
-        File file = buildFile(parent, name, ext);
-
-        // If conflicting file, try adding counter suffix
-        int n = 0;
-        while (file.exists()) {
-            if (n++ >= 32) {
-                throw new FileNotFoundException("Failed to create unique file");
-            }
-            file = buildFile(parent, name + " (" + n + ")", ext);
-        }
-
-        return file;
-    }
-
     @Override
     public String renameDocument(String docId, String displayName) throws FileNotFoundException {
         // Since this provider treats renames as generating a completely new
diff --git a/packages/ExternalStorageProvider/tests/Android.mk b/packages/ExternalStorageProvider/tests/Android.mk
deleted file mode 100644
index 830731a..0000000
--- a/packages/ExternalStorageProvider/tests/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
-LOCAL_PACKAGE_NAME := ExternalStorageProviderTests
-LOCAL_INSTRUMENTATION_FOR := ExternalStorageProvider
-
-LOCAL_CERTIFICATE := platform
-
-include $(BUILD_PACKAGE)
diff --git a/packages/ExternalStorageProvider/tests/AndroidManifest.xml b/packages/ExternalStorageProvider/tests/AndroidManifest.xml
deleted file mode 100644
index ffcd499..0000000
--- a/packages/ExternalStorageProvider/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.externalstorage.tests">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
-        android:targetPackage="com.android.externalstorage"
-        android:label="Tests for ExternalStorageProvider" />
-
-</manifest>
diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
deleted file mode 100644
index f980b60..0000000
--- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2013 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.externalstorage;
-
-import static com.android.externalstorage.ExternalStorageProvider.buildUniqueFile;
-
-import android.os.FileUtils;
-import android.provider.DocumentsContract.Document;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import java.io.File;
-
-@MediumTest
-public class ExternalStorageProviderTest extends AndroidTestCase {
-
-    private File mTarget;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mTarget = getContext().getFilesDir();
-        FileUtils.deleteContents(mTarget);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        FileUtils.deleteContents(mTarget);
-    }
-
-    public void testBuildUniqueFile_normal() throws Exception {
-        assertNameEquals("test.jpg", buildUniqueFile(mTarget, "image/jpeg", "test"));
-        assertNameEquals("test.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
-        assertNameEquals("test.jpeg", buildUniqueFile(mTarget, "image/jpeg", "test.jpeg"));
-        assertNameEquals("TEst.JPeg", buildUniqueFile(mTarget, "image/jpeg", "TEst.JPeg"));
-        assertNameEquals("test.png.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.png.jpg"));
-        assertNameEquals("test.png.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.png"));
-
-        assertNameEquals("test.flac", buildUniqueFile(mTarget, "audio/flac", "test"));
-        assertNameEquals("test.flac", buildUniqueFile(mTarget, "audio/flac", "test.flac"));
-        assertNameEquals("test.flac", buildUniqueFile(mTarget, "application/x-flac", "test"));
-        assertNameEquals("test.flac", buildUniqueFile(mTarget, "application/x-flac", "test.flac"));
-    }
-
-    public void testBuildUniqueFile_unknown() throws Exception {
-        assertNameEquals("test", buildUniqueFile(mTarget, "application/octet-stream", "test"));
-        assertNameEquals("test.jpg", buildUniqueFile(mTarget, "application/octet-stream", "test.jpg"));
-        assertNameEquals(".test", buildUniqueFile(mTarget, "application/octet-stream", ".test"));
-
-        assertNameEquals("test", buildUniqueFile(mTarget, "lolz/lolz", "test"));
-        assertNameEquals("test.lolz", buildUniqueFile(mTarget, "lolz/lolz", "test.lolz"));
-    }
-
-    public void testBuildUniqueFile_dir() throws Exception {
-        assertNameEquals("test", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
-        new File(mTarget, "test").mkdir();
-        assertNameEquals("test (1)", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
-
-        assertNameEquals("test.jpg", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg"));
-        new File(mTarget, "test.jpg").mkdir();
-        assertNameEquals("test.jpg (1)", buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg"));
-    }
-
-    public void testBuildUniqueFile_increment() throws Exception {
-        assertNameEquals("test.jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
-        new File(mTarget, "test.jpg").createNewFile();
-        assertNameEquals("test (1).jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
-        new File(mTarget, "test (1).jpg").createNewFile();
-        assertNameEquals("test (2).jpg", buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
-    }
-
-    private static void assertNameEquals(String expected, File actual) {
-        assertEquals(expected, actual.getName());
-    }
-}