diff --git a/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerFragment.java b/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerFragment.java index a573b5d0..2e954bdd 100644 --- a/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerFragment.java +++ b/library/src/main/java/com/nononsenseapps/filepicker/AbstractFilePickerFragment.java @@ -319,14 +319,13 @@ public void onActivityCreated(Bundle savedInstanceState) { } } } - - // If still null - if (mCurrentPath == null) { - mCurrentPath = getRoot(); - } } - refresh(); + // If still null + if (mCurrentPath == null) { + mCurrentPath = getRoot(); + } + refresh(mCurrentPath); } @Override @@ -372,14 +371,17 @@ public void onDetach() { * if permissions are granted and requests them if necessary. See hasPermission() * and handlePermission(). By default, these methods do nothing. Override them if * you need to request permissions at runtime. + * + * @param nextPath path to list files for */ - protected void refresh() { - if (hasPermission()) { + protected void refresh(@NonNull T nextPath) { + if (hasPermission(nextPath)) { + mCurrentPath = nextPath; isLoading = true; getLoaderManager() .restartLoader(0, null, AbstractFilePickerFragment.this); } else { - handlePermission(); + handlePermission(nextPath); } } @@ -387,8 +389,10 @@ protected void refresh() { * If permission has not been granted yet, this method should request it. *

* Override only if you need to request a permission. + * + * @param path The path for which permission should be requested */ - protected void handlePermission() { + protected void handlePermission(@NonNull T path) { // Nothing to do by default } @@ -396,9 +400,10 @@ protected void handlePermission() { * If your implementation needs to request a specific permission to function, check if it * has been granted here. You should probably also override handlePermission() to request it. * + * @param path the path for which permissions should be checked * @return true if permission has been granted, false otherwise. */ - protected boolean hasPermission() { + protected boolean hasPermission(@NonNull T path) { // Nothing to request by default return true; } @@ -573,10 +578,9 @@ public void onClickDir(@NonNull View view, @NonNull DirViewHolder viewHolder) { */ public void goToDir(@NonNull T file) { if (!isLoading) { - mCurrentPath = file; mCheckedItems.clear(); mCheckedVisibleViewHolders.clear(); - refresh(); + refresh(file); } } diff --git a/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java b/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java index 66c0bc8e..539a22d8 100644 --- a/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java +++ b/library/src/main/java/com/nononsenseapps/filepicker/FilePickerFragment.java @@ -28,6 +28,7 @@ public class FilePickerFragment extends AbstractFilePickerFragment { protected static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; protected boolean showHiddenItems = false; + private File mRequestedPath = null; public FilePickerFragment() { } @@ -55,7 +56,7 @@ public boolean areHiddenItemsShown(){ * @return true if app has been granted permission to write to the SD-card. */ @Override - protected boolean hasPermission() { + protected boolean hasPermission(@NonNull File path) { return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE); @@ -65,13 +66,14 @@ protected boolean hasPermission() { * Request permission to write to the SD-card. */ @Override - protected void handlePermission() { + protected void handlePermission(@NonNull File path) { // Should we show an explanation? // if (shouldShowRequestPermissionRationale( // Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Explain to the user why we need permission // } + mRequestedPath = path; requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); } @@ -97,7 +99,9 @@ public void onRequestPermissionsResult(int requestCode, } else { // if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) { if (PackageManager.PERMISSION_GRANTED == grantResults[0]) { // Do refresh - refresh(); + if (mRequestedPath != null) { + refresh(mRequestedPath); + } } else { Toast.makeText(getContext(), R.string.nnf_permission_external_write_denied, Toast.LENGTH_SHORT).show(); @@ -303,8 +307,7 @@ public void onNewFolder(@NonNull final String name) { File folder = new File(mCurrentPath, name); if (folder.mkdir()) { - mCurrentPath = folder; - refresh(); + refresh(folder); } else { Toast.makeText(getActivity(), R.string.nnf_create_folder_error, Toast.LENGTH_SHORT).show(); @@ -322,7 +325,7 @@ public void onNewFolder(@NonNull final String name) { * @return True if item should be added to the list, false otherwise */ protected boolean isItemVisible(final File file) { - if(!showHiddenItems && file.isHidden()){ + if (!showHiddenItems && file.isHidden()) { return false; } return (isDir(file) || (mode == MODE_FILE || mode == MODE_FILE_AND_DIR)); diff --git a/sample/build.gradle b/sample/build.gradle index a3b3760f..eacc3616 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -47,4 +47,7 @@ dependencies { // FTP browser sample compile 'commons-net:commons-net:3.3' + + // Root example + compile 'eu.chainfire:libsuperuser:1.0.0.+' } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 4e1e97c4..c80c3bb9 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -62,6 +62,26 @@ + + + + + + + + + + + + + + { private final DropboxAPI dbApi; - private FolderCreator folderCreator; private ProgressBar progressBar; private RecyclerView recyclerView; @@ -114,10 +113,12 @@ public void onClick(final View v) { /** * If we are loading, then hide the list and show the progress bar instead. + * + * @param nextPath path to list files for */ @Override - protected void refresh() { - super.refresh(); + protected void refresh(DropboxAPI.Entry nextPath) { + super.refresh(nextPath); if (isLoading) { progressBar.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.INVISIBLE); diff --git a/sample/src/main/java/com/nononsenseapps/filepicker/sample/ftp/FtpPickerFragment.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/ftp/FtpPickerFragment.java index b2e64a6f..c87d860e 100644 --- a/sample/src/main/java/com/nononsenseapps/filepicker/sample/ftp/FtpPickerFragment.java +++ b/sample/src/main/java/com/nononsenseapps/filepicker/sample/ftp/FtpPickerFragment.java @@ -320,8 +320,7 @@ protected FtpFile doInBackground(String... names) { @Override protected void onPostExecute(FtpFile folder) { if (folder != null) { - mCurrentPath = folder; - refresh(); + refresh(folder); } else { Toast.makeText(getContext(), R.string.nnf_create_folder_error, Toast.LENGTH_SHORT).show(); } diff --git a/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUErrorFragment.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUErrorFragment.java new file mode 100644 index 00000000..207a688d --- /dev/null +++ b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUErrorFragment.java @@ -0,0 +1,36 @@ +package com.nononsenseapps.filepicker.sample.root; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.AlertDialog; + +/** + * A dialog which tells the user that no SU binary is available + */ +public class SUErrorFragment extends DialogFragment { + + private static final String TAG = "SUErrorFragment"; + + public static void showDialog(@NonNull final FragmentManager fm) { + SUErrorFragment d = new SUErrorFragment(); + d.show(fm, TAG); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage("No read permisson, root unavailable") + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + return builder.create(); + } +} diff --git a/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerActivity.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerActivity.java new file mode 100644 index 00000000..74f85f53 --- /dev/null +++ b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerActivity.java @@ -0,0 +1,30 @@ +package com.nononsenseapps.filepicker.sample.root; + +import android.os.Environment; +import android.support.annotation.Nullable; + +import com.nononsenseapps.filepicker.AbstractFilePickerActivity; +import com.nononsenseapps.filepicker.AbstractFilePickerFragment; + +import java.io.File; + +public class SUPickerActivity extends AbstractFilePickerActivity { + + public SUPickerActivity() { + super(); + } + + @Override + protected AbstractFilePickerFragment getFragment(@Nullable String startPath, + int mode, + boolean allowMultiple, + boolean allowCreateDir) { + AbstractFilePickerFragment fragment = new SUPickerFragment(); + // startPath is allowed to be null. In that case, default folder should be SD-card and + // not "/" + fragment.setArgs( + startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(), + mode, allowMultiple, allowCreateDir); + return fragment; + } +} diff --git a/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerActivity2.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerActivity2.java new file mode 100644 index 00000000..a3499518 --- /dev/null +++ b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerActivity2.java @@ -0,0 +1,7 @@ +package com.nononsenseapps.filepicker.sample.root; + +/** + * Just for second theme + */ +public class SUPickerActivity2 extends SUPickerActivity { +} diff --git a/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerFragment.java b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerFragment.java new file mode 100644 index 00000000..2e4049df --- /dev/null +++ b/sample/src/main/java/com/nononsenseapps/filepicker/sample/root/SUPickerFragment.java @@ -0,0 +1,71 @@ +package com.nononsenseapps.filepicker.sample.root; + +import android.support.annotation.NonNull; +import android.util.Log; + +import com.nononsenseapps.filepicker.FilePickerFragment; + +import java.io.File; +import java.util.List; + +import eu.chainfire.libsuperuser.Shell; + +/** + * An example picker which calls out to LibSU to get Root-permissions to view otherwise hidden files. + */ +public class SUPickerFragment extends FilePickerFragment { + + @Override + protected boolean hasPermission(@NonNull File path) { + // Return the combination of normal file permissions and SU permissions + return super.hasPermission(path) & (!needSUPermission(path) | hasSUPermission()); + } + + @Override + protected void handlePermission(@NonNull File path) { + // Only call super if we don't have normal file permissions + if (!super.hasPermission(path)) { + super.handlePermission(path); + } + // Only if we need SU permissions + if (needSUPermission(path) && !hasSUPermission()) { + handleSUPermission(); + } + } + + private boolean haveReadPermission(@NonNull File file) { + List result = + Shell.SH.run("test -r " + file.getAbsolutePath() + " && echo \"rootsuccess\""); + return result != null && !result.isEmpty() && "rootsuccess".equals(result.get(0)); + } + + private boolean needSUPermission(@NonNull File path) { + return !haveReadPermission(path); + } + + private boolean isSUAvailable() { + return Shell.SU.available(); + } + + private boolean hasSUPermission() { + if (isSUAvailable()) { + List result = Shell.SU.run("ls -l /"); + if (result != null && !result.isEmpty()) { + return true; + } + } + return false; + } + + private void handleSUPermission() { + if (isSUAvailable()) { + // request + String suVersion = Shell.SU.version(false); + String suVersionInternal = Shell.SU.version(true); + Log.d("libsuperuser: ", "suVersion:"+suVersion+" suVersionInternal:"+suVersionInternal); + } else { + // Notify that no root access available + SUErrorFragment.showDialog(getFragmentManager()); + } + } +} 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 6f11c9f7..97e978b4 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 @@ -145,6 +145,17 @@ android:gravity="center" android:text="Pick Dropbox" /> +