diff --git a/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerActivity.java b/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerActivity.java index a8155798..e39aeddc 100644 --- a/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerActivity.java +++ b/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerActivity.java @@ -142,6 +142,14 @@ public void onFilesPicked(@NonNull final List files) { Intent i = new Intent(); i.putExtra(EXTRA_ALLOW_MULTIPLE, true); + // Set as String Extras for all versions + ArrayList 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) { @@ -153,12 +161,6 @@ public void onFilesPicked(@NonNull final List files) { } } i.setClipData(clip); - } else { - ArrayList paths = new ArrayList<>(); - for (Uri file : files) { - paths.add(file.toString()); - } - i.putStringArrayListExtra(EXTRA_PATHS, paths); } setResult(Activity.RESULT_OK, i); diff --git a/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java b/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java index 9619261e..11956a60 100644 --- a/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java +++ b/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java @@ -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; @@ -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); } /** diff --git a/library/src/main/java/com/nononsenseapps/filepicker/Utils.java b/library/src/main/java/com/nononsenseapps/filepicker/Utils.java index 8673a47c..897df54a 100644 --- a/library/src/main/java/com/nononsenseapps/filepicker/Utils.java +++ b/library/src/main/java/com/nononsenseapps/filepicker/Utils.java @@ -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 */ @@ -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; + } } diff --git a/library/src/main/res/xml/nnf_provider_paths.xml b/library/src/main/res/xml/nnf_provider_paths.xml new file mode 100644 index 00000000..96ce4065 --- /dev/null +++ b/library/src/main/res/xml/nnf_provider_paths.xml @@ -0,0 +1,6 @@ + + + + diff --git a/library/src/test/java/com/nononsenseapps/filepicker/UtilsTest.java b/library/src/test/java/com/nononsenseapps/filepicker/UtilsTest.java index 6350e98b..b1d24f87 100644 --- a/library/src/test/java/com/nononsenseapps/filepicker/UtilsTest.java +++ b/library/src/test/java/com/nononsenseapps/filepicker/UtilsTest.java @@ -2,7 +2,7 @@ import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; public class UtilsTest { @@ -46,4 +46,4 @@ public void appendSlashesSlashesSlashes() throws Exception { assertEquals("A/B", Utils.appendPath("A//", "///B")); assertEquals("/", Utils.appendPath("////", "/////")); } -} \ No newline at end of file +} diff --git a/sample/build.gradle b/sample/build.gradle index 6260d462..79488f8e 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -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' @@ -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' diff --git a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/FastScrollerNewFile.java b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/FastScrollerNewFile.java index 6cea7250..77605215 100644 --- a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/FastScrollerNewFile.java +++ b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/FastScrollerNewFile.java @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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"))); } } diff --git a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectMultipleFiles.java b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectMultipleFiles.java new file mode 100644 index 00000000..e078d9d8 --- /dev/null +++ b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectMultipleFiles.java @@ -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 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()); + } +} diff --git a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFile.java b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFile.java index 5aac3684..c4458db0 100644 --- a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFile.java +++ b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFile.java @@ -6,15 +6,6 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.Espresso.pressBack; -import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; -import static android.support.test.espresso.action.ViewActions.*; -import static android.support.test.espresso.assertion.ViewAssertions.*; -import static android.support.test.espresso.matcher.ViewMatchers.*; - -import com.nononsenseapps.filepicker.sample.R; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -22,9 +13,17 @@ import java.io.IOException; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +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.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.hamcrest.Matchers.is; @LargeTest @RunWith(AndroidJUnit4.class) @@ -84,7 +83,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 @@ -118,7 +117,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 @@ -205,7 +204,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 @@ -254,7 +253,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 @@ -303,7 +302,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 @@ -352,7 +351,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 @@ -401,6 +400,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"))); } } diff --git a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFileStartPathIsFile.java b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFileStartPathIsFile.java index e276af02..755b1905 100644 --- a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFileStartPathIsFile.java +++ b/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/SelectNewFileStartPathIsFile.java @@ -85,6 +85,6 @@ public void selectNewFileWithStartPath() throws IOException { appCompatImageButton.perform(click()); ViewInteraction textView = onView(withId(R.id.text)); - textView.check(matches(withText("file:///storage/emulated/0/000000_nonsense-tests/A-dir/file-3.txt"))); + textView.check(matches(withText("/storage/emulated/0/000000_nonsense-tests/A-dir/file-3.txt"))); } } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 3125573f..2cbf7448 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ @@ -31,12 +31,22 @@ - + + + + + @@ -119,22 +129,22 @@ diff --git a/sample/src/main/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePicker.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePicker.java index ea60f37d..fb33aa36 100644 --- a/sample/src/main/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePicker.java +++ b/sample/src/main/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePicker.java @@ -10,22 +10,21 @@ import android.app.Activity; import android.content.ClipData; import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.CheckBox; -import android.widget.RadioGroup; -import android.widget.TextView; -import android.widget.Toast; import com.dropbox.client2.DropboxAPI; import com.dropbox.client2.android.AndroidAuthSession; -import com.nononsenseapps.filepicker.AbstractFilePickerActivity; import com.nononsenseapps.filepicker.AbstractFilePickerFragment; import com.nononsenseapps.filepicker.FilePickerActivity; +import com.nononsenseapps.filepicker.Utils; +import com.nononsenseapps.filepicker.sample.databinding.ActivityNoNonsenseFilePickerBinding; import com.nononsenseapps.filepicker.sample.dropbox.DropboxFilePickerActivity; import com.nononsenseapps.filepicker.sample.dropbox.DropboxFilePickerActivity2; import com.nononsenseapps.filepicker.sample.dropbox.DropboxSyncHelper; @@ -43,42 +42,25 @@ public class NoNonsenseFilePicker extends Activity { + // How to handle multiple return data + public static boolean useClipData = true; + static final int CODE_SD = 0; static final int CODE_DB = 1; static final int CODE_FTP = 2; - TextView textView; DropboxAPI mDBApi = null; - CheckBox checkAllowCreateDir; - CheckBox checkAllowMultiple; - CheckBox checkSingleClick; - CheckBox checkLightTheme; - RadioGroup radioGroup; - CheckBox checkAllowExistingFile; + ActivityNoNonsenseFilePickerBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_no_nonsense_file_picker); - - checkAllowCreateDir = - (CheckBox) findViewById(R.id.checkAllowCreateDir); - checkAllowMultiple = - (CheckBox) findViewById(R.id.checkAllowMultiple); - checkAllowExistingFile = - (CheckBox) findViewById(R.id.checkAllowExistingFile); - checkSingleClick = - (CheckBox) findViewById(R.id.checkSingleClick); - checkLightTheme = - (CheckBox) findViewById(R.id.checkLightTheme); - radioGroup = - (RadioGroup) findViewById(R.id.radioGroup); - textView = (TextView) findViewById(R.id.text); - - findViewById(R.id.button_sd) + binding = DataBindingUtil.setContentView(this, R.layout.activity_no_nonsense_file_picker); + + binding.buttonSd .setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { - if (checkLightTheme.isChecked()) { + if (binding.checkLightTheme.isChecked()) { startActivity(CODE_SD, FilePickerActivity2.class); } else { startActivity(CODE_SD, FilePickerActivity.class); @@ -86,11 +68,11 @@ public void onClick(final View v) { } }); - findViewById(R.id.button_image) + binding.buttonImage .setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { - if (checkLightTheme.isChecked()) { + if (binding.checkLightTheme.isChecked()) { startActivity(CODE_SD, MultimediaPickerActivity2.class); } else { startActivity(CODE_SD, MultimediaPickerActivity.class); @@ -98,11 +80,11 @@ public void onClick(final View v) { } }); - findViewById(R.id.button_ftp) + binding.buttonFtp .setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { - if (checkLightTheme.isChecked()) { + if (binding.checkLightTheme.isChecked()) { startActivity(CODE_FTP, FtpPickerActivity2.class); } else { startActivity(CODE_FTP, FtpPickerActivity.class); @@ -110,7 +92,7 @@ public void onClick(final View v) { } }); - findViewById(R.id.button_dropbox) + binding.buttonDropbox .setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View v) { @@ -126,8 +108,7 @@ public void onClick(final View v) { mDBApi.getSession().startOAuth2Authentication( NoNonsenseFilePicker.this); } else { // User is authorized, open file picker - Intent i; - if (checkLightTheme.isChecked()) { + if (binding.checkLightTheme.isChecked()) { startActivity(CODE_DB, DropboxFilePickerActivity2.class); } else { startActivity(CODE_DB, DropboxFilePickerActivity.class); @@ -136,27 +117,29 @@ public void onClick(final View v) { } }); - findViewById(R.id.button_root).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (checkLightTheme.isChecked()) { - startActivity(CODE_SD, SUPickerActivity.class); - } else { - startActivity(CODE_SD, SUPickerActivity2.class); - } - } - }); - - findViewById(R.id.button_fastscroll).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (checkLightTheme.isChecked()) { - startActivity(CODE_SD, FastScrollerFilePickerActivity.class); - } else { - startActivity(CODE_SD, FastScrollerFilePickerActivity2.class); - } - } - }); + binding.buttonRoot + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (binding.checkLightTheme.isChecked()) { + startActivity(CODE_SD, SUPickerActivity.class); + } else { + startActivity(CODE_SD, SUPickerActivity2.class); + } + } + }); + + binding.buttonFastscroll + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (binding.checkLightTheme.isChecked()) { + startActivity(CODE_SD, FastScrollerFilePickerActivity.class); + } else { + startActivity(CODE_SD, FastScrollerFilePickerActivity2.class); + } + } + }); } protected void startActivity(final int code, final Class klass) { @@ -165,17 +148,17 @@ protected void startActivity(final int code, final Class klass) { i.setAction(Intent.ACTION_GET_CONTENT); i.putExtra(SUPickerActivity.EXTRA_ALLOW_MULTIPLE, - checkAllowMultiple.isChecked()); + binding.checkAllowMultiple.isChecked()); i.putExtra(FilePickerActivity.EXTRA_SINGLE_CLICK, - checkSingleClick.isChecked()); + binding.checkSingleClick.isChecked()); i.putExtra(SUPickerActivity.EXTRA_ALLOW_CREATE_DIR, - checkAllowCreateDir.isChecked()); + binding.checkAllowCreateDir.isChecked()); i.putExtra(FilePickerActivity.EXTRA_ALLOW_EXISTING_FILE, - checkAllowExistingFile.isChecked()); + binding.checkAllowExistingFile.isChecked()); // What mode is selected final int mode; - switch (radioGroup.getCheckedRadioButtonId()) { + switch (binding.radioGroup.getCheckedRadioButtonId()) { case R.id.radioDir: mode = AbstractFilePickerFragment.MODE_DIR; break; @@ -239,38 +222,54 @@ public boolean onOptionsItemSelected(MenuItem item) { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if ((CODE_SD == requestCode || CODE_DB == requestCode || CODE_FTP == requestCode) && - resultCode == Activity.RESULT_OK) { - if (data.getBooleanExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, - false)) { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + // Always check the resultCode! + // Checking for the requestCodes is a bit redundant but good style + if (resultCode == Activity.RESULT_OK && + (CODE_SD == requestCode || CODE_DB == requestCode || CODE_FTP == requestCode)) { + // If we handled multiple files, we need to get the result differently + if (data.getBooleanExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)) { + // This is the typical style on Android 4.2 and above + if (useClipData && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + Log.i("SAMPLEAPP", "onActivityResult: Using ClipData"); ClipData clip = data.getClipData(); StringBuilder sb = new StringBuilder(); + // clip data CAN be null in case of an empty result if (clip != null) { for (int i = 0; i < clip.getItemCount(); i++) { - sb.append(clip.getItemAt(i).getUri().toString()); - sb.append("\n"); + if (i > 0) { + sb.append("\n"); + } + Uri uri = clip.getItemAt(i).getUri(); + sb.append(CODE_SD == requestCode ? + Utils.getFileForUri(uri).toString() : + uri.toString()); } } - textView.setText(sb.toString()); - } else { + binding.text.setText(sb.toString()); + } else /* This style is available in all SDK versions */ { + Log.i("SAMPLEAPP", "onActivityResult: Using StringExtras"); ArrayList paths = data.getStringArrayListExtra( FilePickerActivity.EXTRA_PATHS); StringBuilder sb = new StringBuilder(); if (paths != null) { for (String path : paths) { - sb.append(path); - sb.append("\n"); + if (sb.length() > 0) { + sb.append("\n"); + } + sb.append(CODE_SD == requestCode ? + Utils.getFileForUri(Uri.parse(path)).toString() : + path); } } - textView.setText(sb.toString()); + binding.text.setText(sb.toString()); } - } else { - textView.setText(data.getData().toString()); + } else /* Single file mode */ { + binding.text.setText(CODE_SD == requestCode ? + Utils.getFileForUri(data.getData()).toString() : + data.getDataString()); } } } diff --git a/sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePickerTest.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePickerTest.java similarity index 100% rename from sample/src/androidTestUitests/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePickerTest.java rename to sample/src/main/java/com/nononsenseapps/filepicker/sample/NoNonsenseFilePickerTest.java diff --git a/sample/src/main/res/layout/activity_no_nonsense_file_picker.xml b/sample/src/main/res/layout/activity_no_nonsense_file_picker.xml index 83cb96bf..8b993721 100644 --- a/sample/src/main/res/layout/activity_no_nonsense_file_picker.xml +++ b/sample/src/main/res/layout/activity_no_nonsense_file_picker.xml @@ -3,213 +3,216 @@ ~ License, v. 2.0. If a copy of the MPL was not distributed with this ~ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - - - - - - + + + + + + - + android:layout_gravity="center_horizontal" + android:orientation="vertical"> - + + + + + + + + + + + + + android:text="Multiple items" /> - + android:text="Single selection returns immediately" /> - + android:text="Allow creation of directories" /> - + android:text="Allow selection of existing (new) file" /> - + - + +