Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f9b2ef59 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Guide user towards adoption when card is "empty"."

parents 42a24dcc 2035614a
Loading
Loading
Loading
Loading
+74 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Log;


import java.io.File;
import java.io.File;
import java.util.LinkedList;


/**
/**
 * Provides access to environment variables.
 * Provides access to environment variables.
@@ -609,6 +610,79 @@ public class Environment {
        return false;
        return false;
    }
    }


    /** {@hide} */ public static final int HAS_MUSIC = 1 << 0;
    /** {@hide} */ public static final int HAS_PODCASTS = 1 << 1;
    /** {@hide} */ public static final int HAS_RINGTONES = 1 << 2;
    /** {@hide} */ public static final int HAS_ALARMS = 1 << 3;
    /** {@hide} */ public static final int HAS_NOTIFICATIONS = 1 << 4;
    /** {@hide} */ public static final int HAS_PICTURES = 1 << 5;
    /** {@hide} */ public static final int HAS_MOVIES = 1 << 6;
    /** {@hide} */ public static final int HAS_DOWNLOADS = 1 << 7;
    /** {@hide} */ public static final int HAS_DCIM = 1 << 8;
    /** {@hide} */ public static final int HAS_DOCUMENTS = 1 << 9;

    /** {@hide} */ public static final int HAS_ANDROID = 1 << 16;
    /** {@hide} */ public static final int HAS_OTHER = 1 << 17;

    /**
     * Classify the content types present on the given external storage device.
     * <p>
     * This is typically useful for deciding if an inserted SD card is empty, or
     * if it contains content like photos that should be preserved.
     *
     * @hide
     */
    public static int classifyExternalStorageDirectory(File dir) {
        int res = 0;
        for (File f : FileUtils.listFilesOrEmpty(dir)) {
            if (f.isFile() && isInterestingFile(f)) {
                res |= HAS_OTHER;
            } else if (f.isDirectory() && hasInterestingFiles(f)) {
                final String name = f.getName();
                if (DIRECTORY_MUSIC.equals(name)) res |= HAS_MUSIC;
                else if (DIRECTORY_PODCASTS.equals(name)) res |= HAS_PODCASTS;
                else if (DIRECTORY_RINGTONES.equals(name)) res |= HAS_RINGTONES;
                else if (DIRECTORY_ALARMS.equals(name)) res |= HAS_ALARMS;
                else if (DIRECTORY_NOTIFICATIONS.equals(name)) res |= HAS_NOTIFICATIONS;
                else if (DIRECTORY_PICTURES.equals(name)) res |= HAS_PICTURES;
                else if (DIRECTORY_MOVIES.equals(name)) res |= HAS_MOVIES;
                else if (DIRECTORY_DOWNLOADS.equals(name)) res |= HAS_DOWNLOADS;
                else if (DIRECTORY_DCIM.equals(name)) res |= HAS_DCIM;
                else if (DIRECTORY_DOCUMENTS.equals(name)) res |= HAS_DOCUMENTS;
                else if (DIRECTORY_ANDROID.equals(name)) res |= HAS_ANDROID;
                else res |= HAS_OTHER;
            }
        }
        return res;
    }

    private static boolean hasInterestingFiles(File dir) {
        final LinkedList<File> explore = new LinkedList<>();
        explore.add(dir);
        while (!explore.isEmpty()) {
            dir = explore.pop();
            for (File f : FileUtils.listFilesOrEmpty(dir)) {
                if (isInterestingFile(f)) return true;
                if (f.isDirectory()) explore.add(f);
            }
        }
        return false;
    }

    private static boolean isInterestingFile(File file) {
        if (file.isFile()) {
            final String name = file.getName().toLowerCase();
            if (name.endsWith(".exe") || name.equals("autorun.inf")
                    || name.equals("launchpad.zip") || name.equals(".nomedia")) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }

    /**
    /**
     * Get a top-level shared/external storage directory for placing files of a
     * Get a top-level shared/external storage directory for placing files of a
     * particular type. This is where the user will typically place and manage
     * particular type. This is where the user will typically place and manage
+103 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.os;

import static android.os.Environment.HAS_ANDROID;
import static android.os.Environment.HAS_DCIM;
import static android.os.Environment.HAS_DOWNLOADS;
import static android.os.Environment.HAS_OTHER;
import static android.os.Environment.classifyExternalStorageDirectory;

import static org.junit.Assert.assertEquals;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.File;

@RunWith(AndroidJUnit4.class)
public class EnvironmentTest {
    private File dir;

    private Context getContext() {
        return InstrumentationRegistry.getContext();
    }

    @Before
    public void setUp() throws Exception {
        dir = getContext().getDir("testing", Context.MODE_PRIVATE);
        FileUtils.deleteContents(dir);
    }

    @After
    public void tearDown() throws Exception {
        FileUtils.deleteContents(dir);
    }

    @Test
    public void testClassify_empty() {
        assertEquals(0, classifyExternalStorageDirectory(dir));
    }

    @Test
    public void testClassify_emptyDirs() {
        Environment.buildPath(dir, "DCIM").mkdirs();
        Environment.buildPath(dir, "DCIM", "January").mkdirs();
        Environment.buildPath(dir, "Downloads").mkdirs();
        Environment.buildPath(dir, "LOST.DIR").mkdirs();
        assertEquals(0, classifyExternalStorageDirectory(dir));
    }

    @Test
    public void testClassify_emptyFactory() throws Exception {
        Environment.buildPath(dir, "autorun.inf").createNewFile();
        Environment.buildPath(dir, "LaunchU3.exe").createNewFile();
        Environment.buildPath(dir, "LaunchPad.zip").createNewFile();
        assertEquals(0, classifyExternalStorageDirectory(dir));
    }

    @Test
    public void testClassify_photos() throws Exception {
        Environment.buildPath(dir, "DCIM").mkdirs();
        Environment.buildPath(dir, "DCIM", "IMG_1024.JPG").createNewFile();
        Environment.buildPath(dir, "Download").mkdirs();
        Environment.buildPath(dir, "Download", "foobar.pdf").createNewFile();
        assertEquals(HAS_DCIM | HAS_DOWNLOADS, classifyExternalStorageDirectory(dir));
    }

    @Test
    public void testClassify_other() throws Exception {
        Environment.buildPath(dir, "Android").mkdirs();
        Environment.buildPath(dir, "Android", "com.example").mkdirs();
        Environment.buildPath(dir, "Android", "com.example", "internal.dat").createNewFile();
        Environment.buildPath(dir, "Linux").mkdirs();
        Environment.buildPath(dir, "Linux", "install-amd64-minimal-20170907.iso").createNewFile();
        assertEquals(HAS_ANDROID | HAS_OTHER, classifyExternalStorageDirectory(dir));
    }

    @Test
    public void testClassify_otherRoot() throws Exception {
        Environment.buildPath(dir, "Taxes.pdf").createNewFile();
        assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir));
    }
}
+40 −9
Original line number Original line Diff line number Diff line
@@ -21,15 +21,24 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import android.content.Context;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Document;
import android.test.AndroidTestCase;
import android.support.test.InstrumentationRegistry;
import android.test.suitebuilder.annotation.MediumTest;
import android.support.test.runner.AndroidJUnit4;


import libcore.io.IoUtils;
import libcore.io.IoUtils;


import com.google.android.collect.Sets;
import com.google.android.collect.Sets;


import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
@@ -37,8 +46,8 @@ import java.io.FileWriter;
import java.util.Arrays;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HashSet;


@MediumTest
@RunWith(AndroidJUnit4.class)
public class FileUtilsTest extends AndroidTestCase {
public class FileUtilsTest {
    private static final String TEST_DATA =
    private static final String TEST_DATA =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";


@@ -47,10 +56,12 @@ public class FileUtilsTest extends AndroidTestCase {
    private File mCopyFile;
    private File mCopyFile;
    private File mTarget;
    private File mTarget;


    private Context getContext() {
        return InstrumentationRegistry.getContext();
    }


    @Override
    @Before
    protected void setUp() throws Exception {
    public void setUp() throws Exception {
        super.setUp();
        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
        mTestFile = new File(mDir, "test.file");
        mTestFile = new File(mDir, "test.file");
        mCopyFile = new File(mDir, "copy.file");
        mCopyFile = new File(mDir, "copy.file");
@@ -59,14 +70,15 @@ public class FileUtilsTest extends AndroidTestCase {
        FileUtils.deleteContents(mTarget);
        FileUtils.deleteContents(mTarget);
    }
    }


    @Override
    @After
    protected void tearDown() throws Exception {
    public void tearDown() throws Exception {
        IoUtils.deleteContents(mDir);
        IoUtils.deleteContents(mDir);
        FileUtils.deleteContents(mTarget);
        FileUtils.deleteContents(mTarget);
    }
    }


    // TODO: test setPermissions(), getPermissions()
    // TODO: test setPermissions(), getPermissions()


    @Test
    public void testCopyFile() throws Exception {
    public void testCopyFile() throws Exception {
        stageFile(mTestFile, TEST_DATA);
        stageFile(mTestFile, TEST_DATA);
        assertFalse(mCopyFile.exists());
        assertFalse(mCopyFile.exists());
@@ -75,6 +87,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null));
        assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null));
    }
    }


    @Test
    public void testCopyToFile() throws Exception {
    public void testCopyToFile() throws Exception {
        final String s = "Foo Bar";
        final String s = "Foo Bar";
        assertFalse(mCopyFile.exists());
        assertFalse(mCopyFile.exists());
@@ -83,6 +96,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null));
        assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null));
    }
    }


    @Test
    public void testIsFilenameSafe() throws Exception {
    public void testIsFilenameSafe() throws Exception {
        assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
        assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
        assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23")));
        assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23")));
@@ -90,6 +104,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar")));
        assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar")));
    }
    }


    @Test
    public void testReadTextFile() throws Exception {
    public void testReadTextFile() throws Exception {
        stageFile(mTestFile, TEST_DATA);
        stageFile(mTestFile, TEST_DATA);


@@ -110,6 +125,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>"));
        assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>"));
    }
    }


    @Test
    public void testReadTextFileWithZeroLengthFile() throws Exception {
    public void testReadTextFileWithZeroLengthFile() throws Exception {
        stageFile(mTestFile, TEST_DATA);
        stageFile(mTestFile, TEST_DATA);
        new FileOutputStream(mTestFile).close();  // Zero out the file
        new FileOutputStream(mTestFile).close();  // Zero out the file
@@ -120,6 +136,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
        assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
    }
    }


    @Test
    public void testContains() throws Exception {
    public void testContains() throws Exception {
        assertTrue(FileUtils.contains(new File("/"), new File("/moo.txt")));
        assertTrue(FileUtils.contains(new File("/"), new File("/moo.txt")));
        assertTrue(FileUtils.contains(new File("/"), new File("/")));
        assertTrue(FileUtils.contains(new File("/"), new File("/")));
@@ -137,11 +154,13 @@ public class FileUtilsTest extends AndroidTestCase {
        assertFalse(FileUtils.contains(new File("/sdcard/"), new File("/sdcard.txt")));
        assertFalse(FileUtils.contains(new File("/sdcard/"), new File("/sdcard.txt")));
    }
    }


    @Test
    public void testDeleteOlderEmptyDir() throws Exception {
    public void testDeleteOlderEmptyDir() throws Exception {
        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
        assertDirContents();
        assertDirContents();
    }
    }


    @Test
    public void testDeleteOlderTypical() throws Exception {
    public void testDeleteOlderTypical() throws Exception {
        touch("file1", HOUR_IN_MILLIS);
        touch("file1", HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
@@ -152,6 +171,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertDirContents("file1", "file2", "file3");
        assertDirContents("file1", "file2", "file3");
    }
    }


    @Test
    public void testDeleteOlderInFuture() throws Exception {
    public void testDeleteOlderInFuture() throws Exception {
        touch("file1", -HOUR_IN_MILLIS);
        touch("file1", -HOUR_IN_MILLIS);
        touch("file2", HOUR_IN_MILLIS);
        touch("file2", HOUR_IN_MILLIS);
@@ -166,6 +186,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertDirContents("file1", "file2");
        assertDirContents("file1", "file2");
    }
    }


    @Test
    public void testDeleteOlderOnlyAge() throws Exception {
    public void testDeleteOlderOnlyAge() throws Exception {
        touch("file1", HOUR_IN_MILLIS);
        touch("file1", HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
@@ -177,6 +198,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertDirContents("file1");
        assertDirContents("file1");
    }
    }


    @Test
    public void testDeleteOlderOnlyCount() throws Exception {
    public void testDeleteOlderOnlyCount() throws Exception {
        touch("file1", HOUR_IN_MILLIS);
        touch("file1", HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
@@ -188,6 +210,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertDirContents("file1", "file2");
        assertDirContents("file1", "file2");
    }
    }


    @Test
    public void testValidExtFilename() throws Exception {
    public void testValidExtFilename() throws Exception {
        assertTrue(FileUtils.isValidExtFilename("a"));
        assertTrue(FileUtils.isValidExtFilename("a"));
        assertTrue(FileUtils.isValidExtFilename("foo.bar"));
        assertTrue(FileUtils.isValidExtFilename("foo.bar"));
@@ -208,6 +231,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals("foo.bar", FileUtils.buildValidExtFilename("foo.bar"));
        assertEquals("foo.bar", FileUtils.buildValidExtFilename("foo.bar"));
    }
    }


    @Test
    public void testValidFatFilename() throws Exception {
    public void testValidFatFilename() throws Exception {
        assertTrue(FileUtils.isValidFatFilename("a"));
        assertTrue(FileUtils.isValidFatFilename("a"));
        assertTrue(FileUtils.isValidFatFilename("foo bar.baz"));
        assertTrue(FileUtils.isValidFatFilename("foo bar.baz"));
@@ -233,6 +257,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals("foo_bar__baz", FileUtils.buildValidFatFilename("foo?bar**baz"));
        assertEquals("foo_bar__baz", FileUtils.buildValidFatFilename("foo?bar**baz"));
    }
    }


    @Test
    public void testTrimFilename() throws Exception {
    public void testTrimFilename() throws Exception {
        assertEquals("short.txt", FileUtils.trimFilename("short.txt", 16));
        assertEquals("short.txt", FileUtils.trimFilename("short.txt", 16));
        assertEquals("extrem...eme.txt", FileUtils.trimFilename("extremelylongfilename.txt", 16));
        assertEquals("extrem...eme.txt", FileUtils.trimFilename("extremelylongfilename.txt", 16));
@@ -245,6 +270,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertEquals("a...z", FileUtils.trimFilename(unicode, 6));
        assertEquals("a...z", FileUtils.trimFilename(unicode, 6));
    }
    }


    @Test
    public void testBuildUniqueFile_normal() throws Exception {
    public void testBuildUniqueFile_normal() throws Exception {
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
@@ -263,6 +289,7 @@ public class FileUtilsTest extends AndroidTestCase {
                FileUtils.buildUniqueFile(mTarget, "application/x-flac", "test.flac"));
                FileUtils.buildUniqueFile(mTarget, "application/x-flac", "test.flac"));
    }
    }


    @Test
    public void testBuildUniqueFile_unknown() throws Exception {
    public void testBuildUniqueFile_unknown() throws Exception {
        assertNameEquals("test",
        assertNameEquals("test",
                FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
                FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
@@ -275,6 +302,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertNameEquals("test.lolz", FileUtils.buildUniqueFile(mTarget, "lolz/lolz", "test.lolz"));
        assertNameEquals("test.lolz", FileUtils.buildUniqueFile(mTarget, "lolz/lolz", "test.lolz"));
    }
    }


    @Test
    public void testBuildUniqueFile_dir() throws Exception {
    public void testBuildUniqueFile_dir() throws Exception {
        assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
        assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
        new File(mTarget, "test").mkdir();
        new File(mTarget, "test").mkdir();
@@ -288,6 +316,7 @@ public class FileUtilsTest extends AndroidTestCase {
                FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg"));
                FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test.jpg"));
    }
    }


    @Test
    public void testBuildUniqueFile_increment() throws Exception {
    public void testBuildUniqueFile_increment() throws Exception {
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
        new File(mTarget, "test.jpg").createNewFile();
        new File(mTarget, "test.jpg").createNewFile();
@@ -298,6 +327,7 @@ public class FileUtilsTest extends AndroidTestCase {
                FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
                FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
    }
    }


    @Test
    public void testBuildUniqueFile_mimeless() throws Exception {
    public void testBuildUniqueFile_mimeless() throws Exception {
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg"));
        assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg"));
        new File(mTarget, "test.jpg").createNewFile();
        new File(mTarget, "test.jpg").createNewFile();
@@ -312,6 +342,7 @@ public class FileUtilsTest extends AndroidTestCase {
        assertNameEquals("test.foo (1).bar", FileUtils.buildUniqueFile(mTarget, "test.foo.bar"));
        assertNameEquals("test.foo (1).bar", FileUtils.buildUniqueFile(mTarget, "test.foo.bar"));
    }
    }


    @Test
    public void testRoundStorageSize() throws Exception {
    public void testRoundStorageSize() throws Exception {
        final long M128 = 128000000L;
        final long M128 = 128000000L;
        final long M256 = 256000000L;
        final long M256 = 256000000L;