Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #118 from spacecowboy/4-nougat
Browse files Browse the repository at this point in the history
Fixed `FileUriExposed` error on Nougat properly
  • Loading branch information
spacecowboy authored Nov 9, 2016
2 parents c00aee8 + 464f6c4 commit 3c61a39
Show file tree
Hide file tree
Showing 14 changed files with 470 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ public void onFilesPicked(@NonNull final List<Uri> files) {
Intent i = new Intent();
i.putExtra(EXTRA_ALLOW_MULTIPLE, true);

// Set as String Extras for all versions
ArrayList<String> paths = new ArrayList<>();
for (Uri file : files) {
paths.add(file.toString());
}
i.putStringArrayListExtra(EXTRA_PATHS, paths);

// Set as Clip Data for Jelly bean and above
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ClipData clip = null;
for (Uri file : files) {
Expand All @@ -153,12 +161,6 @@ public void onFilesPicked(@NonNull final List<Uri> files) {
}
}
i.setClipData(clip);
} else {
ArrayList<String> paths = new ArrayList<>();
for (Uri file : files) {
paths.add(file.toString());
}
i.putStringArrayListExtra(EXTRA_PATHS, paths);
}

setResult(Activity.RESULT_OK, i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.support.annotation.NonNull;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v4.content.Loader;
import android.support.v7.util.SortedList;
import android.support.v7.widget.util.SortedListAdapterCallback;
Expand Down Expand Up @@ -196,7 +197,10 @@ public File getRoot() {
@NonNull
@Override
public Uri toUri(@NonNull final File file) {
return Uri.fromFile(file);
return FileProvider
.getUriForFile(getContext(),
getContext().getApplicationContext().getPackageName() + ".provider",
file);
}

/**
Expand Down
43 changes: 43 additions & 0 deletions library/src/main/java/com/nononsenseapps/filepicker/Utils.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.nononsenseapps.filepicker;

import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import java.io.File;
import java.io.IOException;

/**
* Some utility methods
*/
Expand Down Expand Up @@ -46,4 +50,43 @@ public static String appendPath(@NonNull String first,
return result;
}
}

/**
* Convert a uri generated by a fileprovider, like content://AUTHORITY/ROOT/actual/path
* to a file pointing to file:///actual/path
*
* Note that it only works for paths generated with `ROOT` as the path element. This is done if
* nnf_provider_paths.xml is used to define the file provider in the manifest.
*
* @param uri generated from a file provider
* @return Corresponding {@link File} object
*/
@NonNull
public static File getFileForUri(@NonNull Uri uri) {
String path = uri.getEncodedPath();
final int splitIndex = path.indexOf('/', 1);
final String tag = Uri.decode(path.substring(1, splitIndex));
path = Uri.decode(path.substring(splitIndex + 1));

if (!"root".equalsIgnoreCase(tag)) {
throw new IllegalArgumentException(
String.format("Can't decode paths to '%s', only for 'root' paths.",
tag));
}

final File root = new File("/");

File file = new File(root, path);
try {
file = file.getCanonicalFile();
} catch (IOException e) {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
}

if (!file.getPath().startsWith(root.getPath())) {
throw new SecurityException("Resolved path jumped beyond configured root");
}

return file;
}
}
6 changes: 6 additions & 0 deletions library/src/main/res/xml/nnf_provider_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path
name="root"
path="." />
</paths>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.junit.Test;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;

public class UtilsTest {

Expand Down Expand Up @@ -46,4 +46,4 @@ public void appendSlashesSlashesSlashes() throws Exception {
assertEquals("A/B", Utils.appendPath("A//", "///B"));
assertEquals("/", Utils.appendPath("////", "/////"));
}
}
}
5 changes: 5 additions & 0 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ android {
compileSdkVersion 24
buildToolsVersion "23.0.3"

dataBinding {
enabled = true
}

packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
Expand Down Expand Up @@ -69,6 +73,7 @@ dependencies {
compile 'com.simplecityapps:recyclerview-fastscroll:1.0.9'

// UI Tests
androidTestCompile 'junit:junit:4.12'
uitestsCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
uitestsCompile 'com.android.support.test:runner:0.5'
uitestsCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
import static android.support.test.espresso.contrib.RecyclerViewActions.scrollTo;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
Expand Down Expand Up @@ -88,7 +87,7 @@ public void selectNewFile() throws IOException {
appCompatImageButton.perform(click());

ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/C-dir/testfile")));
textView.check(matches(withText("/storage/emulated/0/000000_nonsense-tests/C-dir/testfile")));
}

@Test
Expand Down Expand Up @@ -124,7 +123,7 @@ public void withSingleClick() throws IOException {

// Should have returned
ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/B-dir/file-3.txt")));
textView.check(matches(withText("/storage/emulated/0/000000_nonsense-tests/B-dir/file-3.txt")));
}

@Test
Expand Down Expand Up @@ -216,7 +215,7 @@ public void enterFileNameWithPathWhichExists() throws IOException {

// Should have returned
ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/B-dir/file-3.txt")));
textView.check(matches(withText("/storage/emulated/0/000000_nonsense-tests/B-dir/file-3.txt")));
}

@Test
Expand Down Expand Up @@ -267,7 +266,7 @@ public void enterFileNameWithPathWhichDoesNotExist() throws IOException {

// Should have returned
ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/path/to/file")));
textView.check(matches(withText("/storage/emulated/0/000000_nonsense-tests/path/to/file")));
}

@Test
Expand Down Expand Up @@ -318,7 +317,7 @@ public void enterFileNameWithDotDot() throws IOException {

// Should have returned
ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/../file.txt")));
textView.check(matches(withText("/storage/emulated/0/file.txt")));
}

@Test
Expand Down Expand Up @@ -369,7 +368,7 @@ public void enterFileNameWithDot() throws IOException {

// Should have returned
ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/./file.txt")));
textView.check(matches(withText("/storage/emulated/0/000000_nonsense-tests/file.txt")));
}

@Test
Expand Down Expand Up @@ -420,6 +419,6 @@ public void enterFileNameWithRoot() throws IOException {

// Should have returned
ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(withText("file:///file.txt")));
textView.check(matches(withText("/file.txt")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.nononsenseapps.filepicker.sample;


import android.net.Uri;
import android.support.test.espresso.ViewInteraction;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;

import com.nononsenseapps.filepicker.Utils;

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

import java.io.File;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static com.nononsenseapps.filepicker.sample.PermissionGranter.allowPermissionsIfNeeded;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@LargeTest
@RunWith(AndroidJUnit4.class)
public class SelectMultipleFiles {

@Rule
public ActivityTestRule<NoNonsenseFilePickerTest> mActivityTestRule =
new ActivityTestRule<>(NoNonsenseFilePickerTest.class);

@Before
public void allowPermissions() {
allowPermissionsIfNeeded(mActivityTestRule.getActivity());
}

/**
* Should not throw on Android 24
*/
@Test
public void selectTwoFilesDoesNotThrowWithClipData() {
NoNonsenseFilePicker.useClipData = true;

selectTwoFilesDoesNotThrow();
}

/**
* Should not throw on Android 24
*/
@Test
public void selectTwoFilesDoesNotThrowWithStringExtras() {
NoNonsenseFilePicker.useClipData = false;

selectTwoFilesDoesNotThrow();
}

private void selectTwoFilesDoesNotThrow() {
ViewInteraction radioButton = onView(
allOf(withId(R.id.radioFile), withText("Select file"),
withParent(withId(R.id.radioGroup)),
isDisplayed()));
radioButton.perform(click());

ViewInteraction checkBox = onView(
allOf(withId(R.id.checkAllowMultiple), withText("Multiple items"), isDisplayed()));
checkBox.perform(click());

ViewInteraction button = onView(
allOf(withId(R.id.button_sd), withText("Pick SD-card"), isDisplayed()));
button.perform(click());

ViewInteraction recyclerView = onView(
allOf(withId(android.R.id.list), isDisplayed()));

// Refresh view (into dir, and out again)
recyclerView.perform(actionOnItemAtPosition(1, click()));
recyclerView.perform(actionOnItemAtPosition(0, click()));

// Click on test dir
recyclerView.perform(actionOnItemAtPosition(1, click()));
// sub dir
recyclerView.perform(actionOnItemAtPosition(1, click()));
// Select first two
recyclerView.perform(actionOnItemAtPosition(1, click()));
recyclerView.perform(actionOnItemAtPosition(2, click()));

// Click OK
ViewInteraction okButton = onView(
allOf(withId(R.id.nnf_button_ok),
withParent(allOf(withId(R.id.nnf_button_container),
withParent(withId(R.id.nnf_buttons_container)))),
isDisplayed()));
okButton.perform(click());

ViewInteraction textView = onView(withId(R.id.text));
textView.check(matches(
withText("/storage/emulated/0/000000_nonsense-tests/A-dir/file-1.txt\n" +
"/storage/emulated/0/000000_nonsense-tests/A-dir/file-0.txt")));

// Make sure we can read one
File file =
Utils.getFileForUri(
Uri.parse("content://com.nononsenseapps.filepicker.sample.provider/root/storage/emulated/0/000000_nonsense-tests/A-dir/file-1.txt"));
assertEquals("/storage/emulated/0/000000_nonsense-tests/A-dir/file-1.txt", file.toString());
assertTrue(file.isFile());
assertTrue(file.canRead());
assertTrue(file.canWrite());
}
}
Loading

0 comments on commit 3c61a39

Please sign in to comment.