From fe70162bde237772685e7ec3077e911e795ffdca Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Thu, 13 Oct 2022 23:14:21 +0530 Subject: [PATCH 01/25] fixes #2417 --- .../com/amaze/filemanager/utils/MainActivityHelper.java | 6 +++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index 219fe8a6f8..1bb44719af 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -323,8 +323,12 @@ public void guideDialogForLEXA(String path, int requestCode) { } private void triggerStorageAccessFramework(int requestCode) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - mainActivity.startActivityForResult(intent, requestCode); + + if (intent.resolveActivity(mainActivity.getPackageManager()) != null) + mainActivity.startActivityForResult(intent, requestCode); + else Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT).show(); } public void rename( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5f7bd14fd..fbb015b0d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,5 +790,6 @@ You only need to do this once, until the next time you select a new location for Secure FTP Unavailable Amaze Cloud Plugin not supported with F-Droid version. Please use Play Store version of Amaze. + No app found to handle this intent. Do you have DocumentsUI installed? From 9042807640ddbe3353942ed54b1ebbad870dd8a0 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 14 Oct 2022 23:20:31 +0530 Subject: [PATCH 02/25] suppress warning --- .../java/com/amaze/filemanager/utils/MainActivityHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index 1bb44719af..77dbdc22c7 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -67,6 +67,7 @@ import com.amaze.filemanager.ui.views.WarnableTextInputValidator; import com.leinardi.android.speeddial.SpeedDialView; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; @@ -322,6 +323,7 @@ public void guideDialogForLEXA(String path, int requestCode) { y.show(); } + @SuppressLint("InlinedApi") private void triggerStorageAccessFramework(int requestCode) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); From 797c7c568e5cbb66d63c36da4a06a3459ef8c7a0 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 14 Oct 2022 23:25:12 +0530 Subject: [PATCH 03/25] add more checks whilst triggering Intent.ACTION_OPEN_DOCUMENT_TREE --- .../com/amaze/filemanager/ui/views/drawer/Drawer.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 33a21583ae..c014f76cc2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -93,6 +93,7 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; @@ -806,7 +807,13 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { .setOnClickListener( (v) -> { Intent safIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - mainActivity.startActivityForResult(safIntent, MainActivity.REQUEST_CODE_SAF); + + if (safIntent.resolveActivity(mainActivity.getPackageManager()) != null) + mainActivity.startActivityForResult(safIntent, MainActivity.REQUEST_CODE_SAF); + else + Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT) + .show(); + dialog.dismiss(); }); dialog.show(); From 3bd67445624c1968d33ca42c43584c5b7fbb333e Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sat, 15 Oct 2022 19:58:16 +0530 Subject: [PATCH 04/25] add curly braces --- .../java/com/amaze/filemanager/ui/views/drawer/Drawer.java | 5 +++-- .../com/amaze/filemanager/utils/MainActivityHelper.java | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index c014f76cc2..8cfc428b39 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -808,11 +808,12 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { (v) -> { Intent safIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - if (safIntent.resolveActivity(mainActivity.getPackageManager()) != null) + if (safIntent.resolveActivity(mainActivity.getPackageManager()) != null) { mainActivity.startActivityForResult(safIntent, MainActivity.REQUEST_CODE_SAF); - else + } else { Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT) .show(); + } dialog.dismiss(); }); diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index 77dbdc22c7..8fc8160484 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -328,9 +328,11 @@ private void triggerStorageAccessFramework(int requestCode) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - if (intent.resolveActivity(mainActivity.getPackageManager()) != null) + if (intent.resolveActivity(mainActivity.getPackageManager()) != null) { mainActivity.startActivityForResult(intent, requestCode); - else Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT).show(); + } } public void rename( From 677eff0feacacbcb162fb4ff111151d291f67037 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sat, 15 Oct 2022 22:00:34 +0530 Subject: [PATCH 05/25] convert Toast to Snackbar --- .../com/amaze/filemanager/ui/views/drawer/Drawer.java | 8 ++++++-- .../com/amaze/filemanager/utils/MainActivityHelper.java | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 8cfc428b39..91b870e82a 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -75,6 +75,8 @@ import com.cloudrail.si.services.GoogleDrive; import com.cloudrail.si.services.OneDrive; import com.google.android.material.navigation.NavigationView; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -93,7 +95,6 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; @@ -811,7 +812,10 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { if (safIntent.resolveActivity(mainActivity.getPackageManager()) != null) { mainActivity.startActivityForResult(safIntent, MainActivity.REQUEST_CODE_SAF); } else { - Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT) + Snackbar.make( + mainActivity.findViewById(R.id.drawer_layout), + R.string.no_app_found_intent, + BaseTransientBottomBar.LENGTH_SHORT) .show(); } diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index 8fc8160484..9d59d5c325 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -65,6 +65,8 @@ import com.amaze.filemanager.ui.fragments.TabFragment; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.views.WarnableTextInputValidator; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; import android.annotation.SuppressLint; @@ -331,7 +333,11 @@ private void triggerStorageAccessFramework(int requestCode) { if (intent.resolveActivity(mainActivity.getPackageManager()) != null) { mainActivity.startActivityForResult(intent, requestCode); } else { - Toast.makeText(mainActivity, R.string.no_app_found, Toast.LENGTH_SHORT).show(); + Snackbar.make( + mainActivity.findViewById(R.id.drawer_layout), + R.string.no_app_found_intent, + BaseTransientBottomBar.LENGTH_SHORT) + .show(); } } From b7791d016f79ca56803c9424cadec7f5db69abc0 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sat, 15 Oct 2022 22:05:58 +0530 Subject: [PATCH 06/25] add more checks to everywhere Intent.ACTION_OPEN_DOCUMENT_TREE is being used --- .../ui/fragments/FtpServerFragment.kt | 67 +++++++++++++------ .../ui/fragments/MainFragment.java | 13 +++- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt index 9dbc3e1ff9..af4d2b7abd 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt @@ -224,9 +224,20 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { } R.id.ftp_path -> { if (shouldUseSafFileSystem()) { - activityResultHandlerOnFtpServerPathUpdate.launch( - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - ) + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + + if (intent.resolveActivity(mainActivity.packageManager) != null) { + activityResultHandlerOnFtpServerPathUpdate.launch( + intent + ) + } else { + Snackbar.make( + mainActivity.findViewById(R.id.drawer_layout), + R.string.no_app_found_intent, + BaseTransientBottomBar.LENGTH_SHORT + ) + .show() + } } else { val dialogBuilder = FolderChooserDialog.Builder(requireActivity()) dialogBuilder @@ -449,28 +460,40 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { .positiveColor(accentColor) .negativeText(R.string.cancel) .negativeColor(accentColor) - .onPositive { dialog, _ -> - activityResultHandlerOnFtpServerPathGrantedSafAccess.launch( - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).also { - if (SDK_INT >= O && - directoryUri.startsWith(defaultPathFromPreferences) - ) { - it.putExtra( - EXTRA_INITIAL_URI, - DocumentsContract.buildDocumentUri( - "com.android.externalstorage.documents", - "primary:" + - directoryUri - .substringAfter( - defaultPathFromPreferences - ) + .onPositive(fun(dialog: MaterialDialog, _: DialogAction) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + + if (intent.resolveActivity(mainActivity.packageManager) != null) { + activityResultHandlerOnFtpServerPathGrantedSafAccess.launch( + intent.also { + if (SDK_INT >= O && + directoryUri.startsWith(defaultPathFromPreferences) + ) { + it.putExtra( + EXTRA_INITIAL_URI, + DocumentsContract.buildDocumentUri( + "com.android.externalstorage.documents", + "primary:" + + directoryUri + .substringAfter( + defaultPathFromPreferences + ) + ) ) - ) + } } - } - ) + ) + } else { + Snackbar.make( + mainActivity.findViewById(R.id.drawer_layout), + R.string.no_app_found_intent, + BaseTransientBottomBar.LENGTH_SHORT + ) + .show() + } + dialog.dismiss() - }.build().show() + }).build().show() } } else { callback.invoke() diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index 62e78295a6..b7ae8c4275 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -82,6 +82,8 @@ import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.Utils; import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import android.content.BroadcastReceiver; import android.content.Context; @@ -647,7 +649,16 @@ private OpenMode loadPathInQ(String actualPath, String providedPath) { d.getActionButton(DialogAction.POSITIVE) .setOnClickListener( v -> { - handleDocumentUriForRestrictedDirectories.launch(intent); + if (intent.resolveActivity(getMainActivity().getPackageManager()) != null) { + handleDocumentUriForRestrictedDirectories.launch(intent); + } else { + Snackbar.make( + getMainActivity().findViewById(R.id.drawer_layout), + R.string.no_app_found_intent, + BaseTransientBottomBar.LENGTH_SHORT) + .show(); + } + d.dismiss(); }); d.show(); From 4a63566ac041d2554702782b2c2baef73b8996a9 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 26 Oct 2022 20:47:58 +0530 Subject: [PATCH 07/25] move checks to an extension function --- .../com/amaze/filemanager/ui/Extensions.kt | 15 +++++++++++++++ .../ui/fragments/FtpServerFragment.kt | 19 +++---------------- .../ui/fragments/MainFragment.java | 16 +++++----------- .../filemanager/ui/views/drawer/Drawer.java | 18 +++++++----------- .../filemanager/utils/MainActivityHelper.java | 14 +++----------- 5 files changed, 33 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt index 658d76e072..ca484d41cf 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt @@ -31,6 +31,9 @@ import android.widget.EditText import android.widget.Toast import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.ui.activities.MainActivity +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputLayout import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -112,3 +115,15 @@ fun View.showFade(duration: Long) { this.animate().alpha(1f).duration = duration this.visibility = View.VISIBLE } + +fun Intent.runIfDocumentsUIExists(mainActivity: MainActivity, callback: Runnable) { + if (this.resolveActivity(mainActivity.packageManager) != null) { + callback.run() + } else { + Snackbar.make( + mainActivity.findViewById(R.id.drawer_layout), + R.string.no_app_found_intent, + BaseTransientBottomBar.LENGTH_SHORT + ).show() + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt index af4d2b7abd..aa2a916783 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt @@ -78,6 +78,7 @@ import com.amaze.filemanager.databinding.FragmentFtpBinding import com.amaze.filemanager.filesystem.files.FileUtils import com.amaze.filemanager.ui.activities.MainActivity import com.amaze.filemanager.ui.notifications.FtpNotification +import com.amaze.filemanager.ui.runIfDocumentsUIExists import com.amaze.filemanager.ui.theme.AppTheme import com.amaze.filemanager.utils.OneCharacterCharSequence import com.amaze.filemanager.utils.PasswordUtil @@ -226,17 +227,10 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { if (shouldUseSafFileSystem()) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - if (intent.resolveActivity(mainActivity.packageManager) != null) { + intent.runIfDocumentsUIExists(mainActivity) { activityResultHandlerOnFtpServerPathUpdate.launch( intent ) - } else { - Snackbar.make( - mainActivity.findViewById(R.id.drawer_layout), - R.string.no_app_found_intent, - BaseTransientBottomBar.LENGTH_SHORT - ) - .show() } } else { val dialogBuilder = FolderChooserDialog.Builder(requireActivity()) @@ -463,7 +457,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { .onPositive(fun(dialog: MaterialDialog, _: DialogAction) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - if (intent.resolveActivity(mainActivity.packageManager) != null) { + intent.runIfDocumentsUIExists(mainActivity) { activityResultHandlerOnFtpServerPathGrantedSafAccess.launch( intent.also { if (SDK_INT >= O && @@ -483,13 +477,6 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { } } ) - } else { - Snackbar.make( - mainActivity.findViewById(R.id.drawer_layout), - R.string.no_app_found_intent, - BaseTransientBottomBar.LENGTH_SHORT - ) - .show() } dialog.dismiss() diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index b7ae8c4275..5e6b6e4d67 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -62,6 +62,7 @@ import com.amaze.filemanager.filesystem.files.EncryptDecryptUtils; import com.amaze.filemanager.filesystem.files.FileListSorter; import com.amaze.filemanager.filesystem.files.FileUtils; +import com.amaze.filemanager.ui.ExtensionsKt; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.activities.MainActivityViewModel; import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; @@ -82,8 +83,6 @@ import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.Utils; import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.snackbar.BaseTransientBottomBar; -import com.google.android.material.snackbar.Snackbar; import android.content.BroadcastReceiver; import android.content.Context; @@ -649,15 +648,10 @@ private OpenMode loadPathInQ(String actualPath, String providedPath) { d.getActionButton(DialogAction.POSITIVE) .setOnClickListener( v -> { - if (intent.resolveActivity(getMainActivity().getPackageManager()) != null) { - handleDocumentUriForRestrictedDirectories.launch(intent); - } else { - Snackbar.make( - getMainActivity().findViewById(R.id.drawer_layout), - R.string.no_app_found_intent, - BaseTransientBottomBar.LENGTH_SHORT) - .show(); - } + ExtensionsKt.runIfDocumentsUIExists( + intent, + getMainActivity(), + () -> handleDocumentUriForRestrictedDirectories.launch(intent)); d.dismiss(); }); diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 91b870e82a..64e949971f 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -47,6 +47,7 @@ import com.amaze.filemanager.filesystem.RootHelper; import com.amaze.filemanager.filesystem.cloud.CloudUtil; import com.amaze.filemanager.filesystem.files.FileUtils; +import com.amaze.filemanager.ui.ExtensionsKt; import com.amaze.filemanager.ui.activities.AboutActivity; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.activities.PreferencesActivity; @@ -75,8 +76,6 @@ import com.cloudrail.si.services.GoogleDrive; import com.cloudrail.si.services.OneDrive; import com.google.android.material.navigation.NavigationView; -import com.google.android.material.snackbar.BaseTransientBottomBar; -import com.google.android.material.snackbar.Snackbar; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -809,15 +808,12 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { (v) -> { Intent safIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - if (safIntent.resolveActivity(mainActivity.getPackageManager()) != null) { - mainActivity.startActivityForResult(safIntent, MainActivity.REQUEST_CODE_SAF); - } else { - Snackbar.make( - mainActivity.findViewById(R.id.drawer_layout), - R.string.no_app_found_intent, - BaseTransientBottomBar.LENGTH_SHORT) - .show(); - } + ExtensionsKt.runIfDocumentsUIExists( + safIntent, + mainActivity, + () -> + mainActivity.startActivityForResult( + safIntent, MainActivity.REQUEST_CODE_SAF)); dialog.dismiss(); }); diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index 9d59d5c325..ce03fd7aea 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -58,6 +58,7 @@ import com.amaze.filemanager.filesystem.files.CryptUtil; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils; +import com.amaze.filemanager.ui.ExtensionsKt; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; import com.amaze.filemanager.ui.fragments.MainFragment; @@ -65,8 +66,6 @@ import com.amaze.filemanager.ui.fragments.TabFragment; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.views.WarnableTextInputValidator; -import com.google.android.material.snackbar.BaseTransientBottomBar; -import com.google.android.material.snackbar.Snackbar; import com.leinardi.android.speeddial.SpeedDialView; import android.annotation.SuppressLint; @@ -330,15 +329,8 @@ private void triggerStorageAccessFramework(int requestCode) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - if (intent.resolveActivity(mainActivity.getPackageManager()) != null) { - mainActivity.startActivityForResult(intent, requestCode); - } else { - Snackbar.make( - mainActivity.findViewById(R.id.drawer_layout), - R.string.no_app_found_intent, - BaseTransientBottomBar.LENGTH_SHORT) - .show(); - } + ExtensionsKt.runIfDocumentsUIExists( + intent, mainActivity, () -> mainActivity.startActivityForResult(intent, requestCode)); } public void rename( From 4a98ee73fe4de654557e2e34cd5378e974d90332 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 25 Oct 2022 01:14:31 +0530 Subject: [PATCH 08/25] fix merge conflicts --- .../filemanager/ui/views/drawer/Drawer.java | 67 +++++++++++++++++-- app/src/main/res/layout-v21/main_toolbar.xml | 1 + .../main/res/layout-w720dp/main_toolbar.xml | 1 + app/src/main/res/layout/main_toolbar.xml | 1 + app/src/main/res/values-v21/styles.xml | 3 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 64e949971f..093edfbfca 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -83,7 +83,11 @@ import android.content.res.Configuration; import android.graphics.Color; import android.os.Build; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.format.Formatter; +import android.text.style.RelativeSizeSpan; +import android.text.style.TextAppearanceSpan; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; @@ -98,6 +102,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.FragmentTransaction; @@ -297,6 +302,11 @@ public void refreshDrawer() { String name = storageDirectory.name; int icon = storageDirectory.iconRes; + HybridFile hybridFile = new HybridFile(OpenMode.UNKNOWN, file); + hybridFile.generateMode(mainActivity); + + long totalSpace = hybridFile.getTotal(mainActivity), freeSpace = hybridFile.getUsableSpace(); + storageDirectoryPaths.add(file); if (file.contains(OTGUtil.PREFIX_OTG) || file.startsWith(OTGUtil.PREFIX_MEDIA_REMOVABLE)) { @@ -307,7 +317,9 @@ public void refreshDrawer() { "OTG", new MenuMetadata(file), R.drawable.ic_usb_white_24dp, - R.drawable.ic_show_chart_black_24dp); + R.drawable.ic_show_chart_black_24dp, + Formatter.formatFileSize(mainActivity, freeSpace), + Formatter.formatFileSize(mainActivity, totalSpace)); continue; } @@ -319,7 +331,9 @@ public void refreshDrawer() { name, new MenuMetadata(file), icon, - R.drawable.ic_show_chart_black_24dp); + R.drawable.ic_show_chart_black_24dp, + Formatter.formatFileSize(mainActivity, freeSpace), + Formatter.formatFileSize(mainActivity, totalSpace)); if (phoneStorageCount == 0) firstPath = file; else if (phoneStorageCount == 1) secondPath = file; @@ -666,7 +680,8 @@ private void addNewItem( MenuMetadata meta, @DrawableRes int icon, @DrawableRes Integer actionViewIcon) { - addNewItem(menu, group, order, mainActivity.getString(text), meta, icon, actionViewIcon); + addNewItem( + menu, group, order, mainActivity.getString(text), meta, icon, actionViewIcon, null, null); } private void addNewItem( @@ -677,9 +692,30 @@ private void addNewItem( MenuMetadata meta, @DrawableRes int icon, @DrawableRes Integer actionViewIcon) { + addNewItem(menu, group, order, text, meta, icon, actionViewIcon, null, null); + } + + private void addNewItem( + Menu menu, + int group, + int order, + String text, + MenuMetadata meta, + @DrawableRes int icon, + @DrawableRes Integer actionViewIcon, + @Nullable String freeSpace, + @Nullable String totalSpace) { if (BuildConfig.DEBUG && menu.findItem(order) != null) throw new IllegalStateException("Item already id exists: " + order); - MenuItem item = menu.add(group, order, order, text).setIcon(icon); + + MenuItem item = null; + + if (freeSpace != null && totalSpace != null) + item = + menu.add(group, order, order, getSpannableText(text, freeSpace, totalSpace)) + .setIcon(icon); + else item = menu.add(group, order, order, text).setIcon(icon); + if (TextUtils.isEmpty(meta.path)) { DrawerViewModel model = new ViewModelProvider(mainActivity).get(DrawerViewModel.class); model.putDrawerMetadata(item, meta); @@ -699,7 +735,9 @@ private void addNewItem( if (!mainActivity.getAppTheme().equals(AppTheme.LIGHT)) { imageView.setColorFilter(Color.WHITE); } - item.getActionView().setOnClickListener((view) -> onNavigationItemActionClick(item)); + + MenuItem finalItem = item; + item.getActionView().setOnClickListener((view) -> onNavigationItemActionClick(finalItem)); } } @@ -1038,4 +1076,23 @@ public String getSecondPath() { public Billing getBilling() { return this.billing; } + + private SpannableString getSpannableText(String text, String freeSpace, String totalSpace) { + + String s = + String.format( + "%s\n%s %s %s", text, freeSpace, mainActivity.getString(R.string.free_of), totalSpace); + + SpannableString spannableString = new SpannableString(s); + + spannableString.setSpan(new RelativeSizeSpan(0.8f), text.length() + 1, s.length(), 0); + + spannableString.setSpan( + new TextAppearanceSpan(mainActivity, R.style.DrawerItemDriveSizeTextStyle), + text.length() + 1, + s.length(), + 0); + + return spannableString; + } } diff --git a/app/src/main/res/layout-v21/main_toolbar.xml b/app/src/main/res/layout-v21/main_toolbar.xml index d7c1e69b12..f183bb5eb8 100644 --- a/app/src/main/res/layout-v21/main_toolbar.xml +++ b/app/src/main/res/layout-v21/main_toolbar.xml @@ -103,6 +103,7 @@ diff --git a/app/src/main/res/layout-w720dp/main_toolbar.xml b/app/src/main/res/layout-w720dp/main_toolbar.xml index 023b6a7004..9a4699712e 100644 --- a/app/src/main/res/layout-w720dp/main_toolbar.xml +++ b/app/src/main/res/layout-w720dp/main_toolbar.xml @@ -111,6 +111,7 @@ diff --git a/app/src/main/res/layout/main_toolbar.xml b/app/src/main/res/layout/main_toolbar.xml index 7f61230a91..dde65b998c 100644 --- a/app/src/main/res/layout/main_toolbar.xml +++ b/app/src/main/res/layout/main_toolbar.xml @@ -96,6 +96,7 @@ diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index ea6629b4ea..e518cb63fd 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -94,4 +94,7 @@ false true + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fbb015b0d9..e24ebb90c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -789,6 +789,7 @@ You only need to do this once, until the next time you select a new location for FTP Secure FTP Unavailable + "free of Amaze Cloud Plugin not supported with F-Droid version. Please use Play Store version of Amaze. No app found to handle this intent. Do you have DocumentsUI installed? From 96328e65061998c444569740870d80ae66a767b2 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sat, 5 Nov 2022 13:21:35 +0530 Subject: [PATCH 09/25] fix merge conflicts --- .../java/com/amaze/filemanager/ui/views/drawer/Drawer.java | 4 +--- app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 093edfbfca..0da1f64bd6 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -1079,9 +1079,7 @@ public Billing getBilling() { private SpannableString getSpannableText(String text, String freeSpace, String totalSpace) { - String s = - String.format( - "%s\n%s %s %s", text, freeSpace, mainActivity.getString(R.string.free_of), totalSpace); + String s = mainActivity.getString(R.string.free_of, text, freeSpace, totalSpace); SpannableString spannableString = new SpannableString(s); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e24ebb90c5..27992d766e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -789,6 +789,7 @@ You only need to do this once, until the next time you select a new location for FTP Secure FTP Unavailable + "%s\n%s free of %s "free of Amaze Cloud Plugin not supported with F-Droid version. Please use Play Store version of Amaze. No app found to handle this intent. Do you have DocumentsUI installed? From e516fe85c3baad531b1b89a28146c223931f343a Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sat, 5 Nov 2022 13:23:29 +0530 Subject: [PATCH 10/25] Add NonNull for menu and metadata --- .../java/com/amaze/filemanager/ui/views/drawer/Drawer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 0da1f64bd6..4d03a4b2d2 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -696,11 +696,11 @@ private void addNewItem( } private void addNewItem( - Menu menu, + @NonNull Menu menu, int group, int order, String text, - MenuMetadata meta, + @NonNull MenuMetadata meta, @DrawableRes int icon, @DrawableRes Integer actionViewIcon, @Nullable String freeSpace, From 5b0cca6a5c7a72057e048f835fe9c738dd97e8f9 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 12 Oct 2022 20:09:12 +0530 Subject: [PATCH 11/25] fixes #3410 --- app/src/main/res/layout/fragment_ftp.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_ftp.xml b/app/src/main/res/layout/fragment_ftp.xml index 41f989d4fe..5bccbd4d9c 100644 --- a/app/src/main/res/layout/fragment_ftp.xml +++ b/app/src/main/res/layout/fragment_ftp.xml @@ -122,7 +122,7 @@ android:paddingBottom="@dimen/padding_normal" android:paddingLeft="@dimen/padding_normal" android:paddingRight="@dimen/padding_normal" - android:drawableRight="@drawable/ic_clear_all" + app:drawableRightCompat="@drawable/ic_clear_all" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/text_view_ftp_port"/> From 7e2d25dae223394fe5a3858a5580ccc1be62e562 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sun, 9 Oct 2022 14:39:16 +0800 Subject: [PATCH 12/25] Rename .java to .kt --- .../java/com/amaze/filemanager/utils/{Billing.java => Billing.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/play/java/com/amaze/filemanager/utils/{Billing.java => Billing.kt} (100%) diff --git a/app/src/play/java/com/amaze/filemanager/utils/Billing.java b/app/src/play/java/com/amaze/filemanager/utils/Billing.kt similarity index 100% rename from app/src/play/java/com/amaze/filemanager/utils/Billing.java rename to app/src/play/java/com/amaze/filemanager/utils/Billing.kt From be119b807e482f44d831f5ce9156d621cfed41c3 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sun, 9 Oct 2022 14:39:17 +0800 Subject: [PATCH 13/25] Upgrade Billing library to 5.0.0 --- .../com/amaze/filemanager/utils/Billing.kt | 406 +++++++++--------- build.gradle | 2 +- 2 files changed, 208 insertions(+), 200 deletions(-) diff --git a/app/src/play/java/com/amaze/filemanager/utils/Billing.kt b/app/src/play/java/com/amaze/filemanager/utils/Billing.kt index 85cc224230..c1977048ef 100644 --- a/app/src/play/java/com/amaze/filemanager/utils/Billing.kt +++ b/app/src/play/java/com/amaze/filemanager/utils/Billing.kt @@ -18,239 +18,247 @@ * along with this program. If not, see . */ -package com.amaze.filemanager.utils; +package com.amaze.filemanager.utils -import java.util.ArrayList; -import java.util.List; +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import com.afollestad.materialdialogs.MaterialDialog +import com.amaze.filemanager.BuildConfig +import com.amaze.filemanager.R +import com.amaze.filemanager.adapters.holders.DonationViewHolder +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.databinding.AdapterDonationBinding +import com.amaze.filemanager.ui.activities.superclasses.BasicActivity +import com.android.billingclient.api.BillingClient +import com.android.billingclient.api.BillingClientStateListener +import com.android.billingclient.api.BillingFlowParams +import com.android.billingclient.api.BillingResult +import com.android.billingclient.api.ConsumeParams +import com.android.billingclient.api.ConsumeResponseListener +import com.android.billingclient.api.ProductDetails +import com.android.billingclient.api.Purchase +import com.android.billingclient.api.PurchasesUpdatedListener +import com.android.billingclient.api.QueryProductDetailsParams +import com.android.billingclient.api.QueryProductDetailsParams.Product +import org.slf4j.LoggerFactory +import java.util.concurrent.Callable -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +class Billing(private val activity: BasicActivity) : + RecyclerView.Adapter(), + PurchasesUpdatedListener { -import com.afollestad.materialdialogs.MaterialDialog; -import com.amaze.filemanager.BuildConfig; -import com.amaze.filemanager.R; -import com.amaze.filemanager.adapters.holders.DonationViewHolder; -import com.amaze.filemanager.application.AppConfig; -import com.amaze.filemanager.databinding.AdapterDonationBinding; -import com.amaze.filemanager.ui.activities.superclasses.BasicActivity; -import com.android.billingclient.api.BillingClient; -import com.android.billingclient.api.BillingClientStateListener; -import com.android.billingclient.api.BillingFlowParams; -import com.android.billingclient.api.BillingResult; -import com.android.billingclient.api.ConsumeParams; -import com.android.billingclient.api.ConsumeResponseListener; -import com.android.billingclient.api.Purchase; -import com.android.billingclient.api.PurchasesUpdatedListener; -import com.android.billingclient.api.SkuDetails; -import com.android.billingclient.api.SkuDetailsParams; + private val LOG = LoggerFactory.getLogger(Billing::class.java) -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; + // List of predefined IAP products SKU. + private val productList: List -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.RecyclerView; + // List of IAP products query result. + private lateinit var productDetails: List -public class Billing extends RecyclerView.Adapter - implements PurchasesUpdatedListener { + // create new donations client + private lateinit var billingClient: BillingClient - private final Logger LOG = LoggerFactory.getLogger(Billing.class); + // True if billing service is connected + private var isServiceConnected = false - private BasicActivity activity; - private List skuList; - private List skuDetails; - - // create new donations client - private BillingClient billingClient; - /** True if billing service is connected now. */ - private boolean isServiceConnected; - - public Billing(@NonNull BasicActivity activity) { - this.activity = activity; - - skuList = new ArrayList<>(); - skuList.add("donations"); - skuList.add("donations_2"); - skuList.add("donations_3"); - skuList.add("donations_4"); - - billingClient = - BillingClient.newBuilder(activity).setListener(this).enablePendingPurchases().build(); - initiatePurchaseFlow(); - } - - @Override - public void onPurchasesUpdated(BillingResult response, @Nullable List purchases) { - if (response.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) { - for (Purchase purchase : purchases) { - ConsumeResponseListener listener = - (responseCode1, purchaseToken) -> { - // we consume the purchase, so that user can perform purchase again - Toast.makeText(activity, R.string.donation_thanks, Toast.LENGTH_LONG).show(); - }; - ConsumeParams consumeParams = - ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); - billingClient.consumeAsync(consumeParams, listener); - } + override fun onPurchasesUpdated(response: BillingResult, purchases: List?) { + if (response.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) { + for (purchase in purchases) { + val listener = + ConsumeResponseListener { _: BillingResult?, _: String? -> + // we consume the purchase, so that user can perform purchase again + Toast.makeText(activity, R.string.donation_thanks, Toast.LENGTH_LONG).show() + } + val consumeParams: ConsumeParams = + ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build() + billingClient.consumeAsync(consumeParams, listener) + } + } } - } - /** Start a purchase flow */ - private void initiatePurchaseFlow() { - Runnable purchaseFlowRequest = - () -> { - SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); - params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP); - billingClient.querySkuDetailsAsync( - params.build(), - (responseCode, skuDetailsList) -> { - if (skuDetailsList != null && skuDetailsList.size() > 0) { - // Successfully fetched product details - skuDetails = skuDetailsList; - popProductsList(responseCode, skuDetailsList); + /** Start a purchase flow */ + private fun initiatePurchaseFlow() { + val purchaseFlowRequest = Runnable { + val params = QueryProductDetailsParams.newBuilder().setProductList(productList) + + billingClient.queryProductDetailsAsync( + params.build() + ) { responseCode: BillingResult, queryResult: List -> + if (queryResult.isNotEmpty()) { + // Successfully fetched product details + productDetails = queryResult + popProductsList(responseCode, queryResult) } else { - AppConfig.toast(activity, R.string.error_fetching_google_play_product_list); - if (BuildConfig.DEBUG) { - LOG.warn( - "Error fetching product list - looks like you are running a DEBUG build."); - } + AppConfig.toast(activity, R.string.error_fetching_google_play_product_list) + if (BuildConfig.DEBUG) { + /* ktlint-disable max-line-length */ + LOG.warn( + "Error fetching product list - looks like you are running a DEBUG build." + ) + /* ktlint-enable max-line-length */ + } } - }); - }; - - executeServiceRequest(purchaseFlowRequest); - } - - /** - * Got products list from play store, pop their details - * - * @param response - * @param skuDetailsList - */ - private void popProductsList(BillingResult response, List skuDetailsList) { - if (response.getResponseCode() == BillingClient.BillingResponseCode.OK - && skuDetailsList != null) { - showPaymentsDialog(activity); + } + } + executeServiceRequest(purchaseFlowRequest) } - } - @NonNull - @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View rootView = - AdapterDonationBinding.inflate(LayoutInflater.from(this.activity), parent, false).getRoot(); - return new DonationViewHolder(rootView); - } + private fun createProductWith(productId: String) = + Product.newBuilder() + .setProductId(productId) + .setProductType(BillingClient.ProductType.INAPP) + .build() - @Override - public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (holder instanceof DonationViewHolder && skuDetails.size() > 0) { - String titleRaw = skuDetails.get(position).getTitle(); - ((DonationViewHolder) holder) - .TITLE.setText(titleRaw.subSequence(0, titleRaw.lastIndexOf("("))); - ((DonationViewHolder) holder).SUMMARY.setText(skuDetails.get(position).getDescription()); - ((DonationViewHolder) holder).PRICE.setText(skuDetails.get(position).getPrice()); - ((DonationViewHolder) holder) - .ROOT_VIEW.setOnClickListener( - v -> purchaseProduct.purchaseItem(skuDetails.get(position))); + /** + * Got products list from play store, pop their details + * + * @param response + * @param productDetailsQueryResult + */ + private fun popProductsList( + response: BillingResult, + productDetailsQueryResult: List + ) { + if (response.responseCode == BillingClient.BillingResponseCode.OK && + productDetailsQueryResult.isNotEmpty() + ) { + showPaymentsDialog(activity) + } } - } - @Override - public int getItemCount() { - return skuList.size(); - } - - private interface PurchaseProduct { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val rootView: View = AdapterDonationBinding.inflate( + LayoutInflater.from( + activity + ), + parent, + false + ).root + return DonationViewHolder(rootView) + } - void purchaseItem(SkuDetails skuDetails); + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is DonationViewHolder && productDetails.isNotEmpty()) { + val titleRaw: String = productDetails[position].title + holder.TITLE.text = titleRaw.subSequence( + 0, + titleRaw.lastIndexOf("(") + ) + holder.SUMMARY.text = productDetails[position].description + holder.PRICE.text = productDetails[position].oneTimePurchaseOfferDetails?.formattedPrice + holder.ROOT_VIEW.setOnClickListener { + purchaseProduct.purchaseItem( + productDetails[position] + ) + } + } + } - void purchaseCancel(); - } + override fun getItemCount(): Int = productList.size - private PurchaseProduct purchaseProduct = - new PurchaseProduct() { - @Override - public void purchaseItem(SkuDetails skuDetails) { + private interface PurchaseProduct { + fun purchaseItem(productDetails: ProductDetails) + fun purchaseCancel() + } - BillingFlowParams billingFlowParams = - BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build(); - billingClient.launchBillingFlow(activity, billingFlowParams); + private val purchaseProduct: PurchaseProduct = object : PurchaseProduct { + override fun purchaseItem(productDetailsArg: ProductDetails) { + val billingFlowParams: BillingFlowParams = + BillingFlowParams.newBuilder().setProductDetailsParamsList( + listOf( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productDetailsArg) + .build() + ) + ).build() + billingClient.launchBillingFlow(activity, billingFlowParams) } - @Override - public void purchaseCancel() { - destroyBillingInstance(); + override fun purchaseCancel() { + destroyBillingInstance() } - }; + } - /** - * We executes a connection request to Google Play - * - * @param runnable - */ - private void executeServiceRequest(Runnable runnable) { - if (isServiceConnected) { - runnable.run(); - } else { - // If billing service was disconnected, we try to reconnect 1 time. - // (feel free to introduce your retry policy here). - startServiceConnection(runnable); + init { + productList = listOf( + createProductWith("donations"), + createProductWith("donations_2"), + createProductWith("donations_3"), + createProductWith("donations_4") + ) + billingClient = + BillingClient.newBuilder(activity).setListener(this).enablePendingPurchases().build() + initiatePurchaseFlow() } - } - /** - * Starts a connection to Google Play services - * - * @param executeOnSuccess - */ - private void startServiceConnection(final Runnable executeOnSuccess) { - billingClient.startConnection( - new BillingClientStateListener() { - @Override - public void onBillingSetupFinished(BillingResult billingResponse) { - LOG.debug("Setup finished. Response code: " + billingResponse.getResponseCode()); - if (billingResponse.getResponseCode() == BillingClient.BillingResponseCode.OK) { - isServiceConnected = true; - if (executeOnSuccess != null) { - executeOnSuccess.run(); - } - } - } + /** + * We executes a connection request to Google Play + * + * @param runnable + */ + private fun executeServiceRequest(runnable: Runnable) { + if (isServiceConnected) { + runnable.run() + } else { + // If billing service was disconnected, we try to reconnect 1 time. + // (feel free to introduce your retry policy here). + startServiceConnection(runnable) + } + } - @Override - public void onBillingServiceDisconnected() { - isServiceConnected = false; - } - }); - } + /** + * Starts a connection to Google Play services + * + * @param executeOnSuccess + */ + private fun startServiceConnection(executeOnSuccess: Runnable?) { + billingClient.startConnection( + object : BillingClientStateListener { + override fun onBillingSetupFinished(billingResponse: BillingResult) { + LOG.debug("Setup finished. Response code: ${billingResponse.responseCode}") + if (billingResponse.responseCode == BillingClient.BillingResponseCode.OK) { + isServiceConnected = true + executeOnSuccess?.run() + } + } - public void destroyBillingInstance() { - if (billingClient != null && billingClient.isReady()) { - billingClient.endConnection(); - billingClient = null; + override fun onBillingServiceDisconnected() { + isServiceConnected = false + } + } + ) } - } - private void showPaymentsDialog(final BasicActivity context) { - /* + /** + * Drop the [BillingClient] on purchase process cancels. + */ + fun destroyBillingInstance() { + if (billingClient.isReady) { + billingClient.endConnection() + } + } + + private fun showPaymentsDialog(context: BasicActivity) { + /* * As of Billing library 4.0, all callbacks are running on background thread. * Need to use AppConfig.runInApplicationThread() for UI interactions * * */ - AppConfig.getInstance() - .runInApplicationThread( - () -> { - final MaterialDialog.Builder builder = new MaterialDialog.Builder(context); - builder.title(R.string.donate); - builder.adapter(this, null); - builder.theme(context.getAppTheme().getMaterialDialogTheme(context)); - builder.cancelListener(dialog -> purchaseProduct.purchaseCancel()); - builder.show(); - return null; - }); - } + AppConfig.getInstance() + .runInApplicationThread( + Callable { + val builder: MaterialDialog.Builder = MaterialDialog.Builder(context) + builder.title(R.string.donate) + builder.adapter(this, null) + builder.theme(context.appTheme.getMaterialDialogTheme(context)) + builder.cancelListener { purchaseProduct.purchaseCancel() } + builder.show() + null + } + ) + } } diff --git a/build.gradle b/build.gradle index 4b6833cd4f..0dea2c1fcb 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { slf4jVersion = "1.7.25" mockitoVersion = "3.9.0" mockitoKotlinVersion = "3.2.0" - androidBillingVersion = "4.0.0" + androidBillingVersion = "5.0.0" junrarVersion = "7.4.0" zip4jVersion = "2.6.4" espressoVersion = "3.4.0" From 332558170ed11bdd62fa37c9be9f70eba41b031c Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Tue, 8 Nov 2022 23:36:54 +0800 Subject: [PATCH 14/25] Changes per PR feedback - Add handle for closing donation dialog - Move Toast out of purchases for loop. Although we only buy one item at a time, it's still more logical to perform the UI change after the for loop --- .../java/com/amaze/filemanager/utils/Billing.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/play/java/com/amaze/filemanager/utils/Billing.kt b/app/src/play/java/com/amaze/filemanager/utils/Billing.kt index c1977048ef..9a1e9f1164 100644 --- a/app/src/play/java/com/amaze/filemanager/utils/Billing.kt +++ b/app/src/play/java/com/amaze/filemanager/utils/Billing.kt @@ -64,18 +64,21 @@ class Billing(private val activity: BasicActivity) : // True if billing service is connected private var isServiceConnected = false + private lateinit var donationDialog: MaterialDialog + override fun onPurchasesUpdated(response: BillingResult, purchases: List?) { if (response.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) { for (purchase in purchases) { - val listener = - ConsumeResponseListener { _: BillingResult?, _: String? -> - // we consume the purchase, so that user can perform purchase again - Toast.makeText(activity, R.string.donation_thanks, Toast.LENGTH_LONG).show() - } + val listener = ConsumeResponseListener { _: BillingResult, _: String -> } val consumeParams: ConsumeParams = - ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build() + ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build() billingClient.consumeAsync(consumeParams, listener) } + // we consume the purchase, so that user can perform purchase again + activity.runOnUiThread { + Toast.makeText(activity, R.string.donation_thanks, Toast.LENGTH_LONG).show() + donationDialog.dismiss() + } } } @@ -256,7 +259,7 @@ class Billing(private val activity: BasicActivity) : builder.adapter(this, null) builder.theme(context.appTheme.getMaterialDialogTheme(context)) builder.cancelListener { purchaseProduct.purchaseCancel() } - builder.show() + donationDialog = builder.show() null } ) From 7c66a849148686de4fa28b247ac8a9e243075daa Mon Sep 17 00:00:00 2001 From: TranceLove Date: Fri, 7 Oct 2022 17:53:48 +0800 Subject: [PATCH 15/25] Fix line parsing of ls/stat output To deal with newer versions of toybox and probably busybox as well --- .../filesystem/files/FileUtils.java | 6 +- .../filesystem/files/FileUtilsTest.kt | 65 +++++++++++-------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java index ae0412d4b6..553903cc4b 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java @@ -899,7 +899,7 @@ public static HybridFileParcelable parseName(String line, boolean isStat) { } } int p = getColonPosition(array); - if (p != -1) { + if (p != -1 && (p + 1) != array.length) { date = array[p - 1] + " | " + array[p]; size = array[p - 2]; } else if (isStat) { @@ -917,6 +917,10 @@ public static HybridFileParcelable parseName(String line, boolean isStat) { for (int i = p + 1; i < q; i++) { name.append(" ").append(array[i]); } + // Newer *boxes may introduce full path during stat. Trim down to the very last / + if (name.lastIndexOf("/") > 0) { + name.delete(0, name.lastIndexOf("/") + 1); + } name = new StringBuilder(name.toString().trim()); for (int i = q + 1; i < array.length; i++) { link.append(" ").append(array[i]); diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt index 66061b2648..80085c4017 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt @@ -25,7 +25,9 @@ import android.os.Build.VERSION_CODES.KITKAT import android.os.Build.VERSION_CODES.P import androidx.test.ext.junit.runners.AndroidJUnit4 import com.amaze.filemanager.filesystem.files.FileUtils.getPathsInPath -import org.junit.Assert.* +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -367,13 +369,16 @@ class FileUtilsTest { */ @Test fun testParseStringForHybridFileParcelable() { - val systemTz = TimeZone.getDefault() - TimeZone.setDefault(TimeZone.getTimeZone("UTC")) - + /* ktlint-disable max-line-length */ // ls - val a = "-rwxr-x--- 1 root shell 29431 2009-01-01 08:00 init.rc" - val b = "lrw-r--r-- 1 root root 15 2009-01-01 08:00 product -> /system/product" - val c = "drwxr-xr-x 17 root root 4096 1970-05-19 08:40 system" + val lsLines = arrayOf( + "-rwxr-x--- 1 root shell 29431 2009-01-01 08:00 init.rc", + "lrw-r--r-- 1 root root 15 2009-01-01 08:00 product -> /system/product", + "drwxr-xr-x 17 root root 4096 1970-05-19 08:40 system", + "-r--r--r-- 1 root root 10 1970-01-13 07:32 cpu_variant:arm", + "lrwxrwxrwx 1 root root 0 2022-10-05 15:39 ac -> ../../devices/platform/GFSH0001:00/power_supply/ac", + "lrwxrwxrwx 1 root root 0 2022-10-05 00:16 usb -> ../../devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8150b@2:qcom,qpnp-smb5/power_supply/usb" + ) // stat with old toybox or busybox // val a1 = "-rwxr-x--- 1 shell root 512 2009-01-01 08:00:00.000000000 `init.rc'" @@ -381,29 +386,33 @@ class FileUtilsTest { // val c1 = "drwxr-xr-x 17 root root 512 1970-05-19 08:40:27.269999949 `system'" // stat with new toybox - val a2 = "-rwxr-x--- 1 shell root 512 1230796800 `init.rc'" - val b2 = "lrw-r--r-- 1 root root 512 1230796800 `product' -> `/system/product'" - val c2 = "drwxr-xr-x 17 root root 512 11922027 `system'" + val statLines = arrayOf( + "-rwxr-x--- 1 shell root 512 1230796800 `init.rc'", + "lrw-r--r-- 1 root root 512 1230796800 `product' -> `/system/product'", + "drwxr-xr-x 17 root root 512 11922027 `system'", + "-r--r--r-- 1 root root 512 1035141 `cpu_variant:arm'", + "lrwxrwxrwx 1 root root 512 1664955558 /sys/class/power_supply/ac -> '../../devices/platform/GFSH0001:00/power_supply/ac'", + "lrwxrwxrwx 1 root root 512 1664956626 /sys/class/power_supply/usb -> '../../devices/platform/soc/c440000.qcom,spmi/spmi-0/spmi0-02/c440000.qcom,spmi:qcom,pm8150b@2:qcom,qpnp-smb5/power_supply/usb'" + ) + /* ktlint-enable max-line-length */ - var result1 = FileUtils.parseName(a, false) - var result2 = FileUtils.parseName(a2.replace("('|`)".toRegex(), ""), true) - assertEquals(result1.date, result2.date) - assertEquals(result1.name, result2.name) - assertEquals(result1.path, result2.path) - - result1 = FileUtils.parseName(b, false) - result2 = FileUtils.parseName(b2.replace("('|`)".toRegex(), ""), true) - assertEquals(result1.date, result2.date) - assertEquals(result1.name, result2.name) - assertEquals(result1.path, result2.path) - assertEquals(result1.link, result2.link) + val systemTz = TimeZone.getDefault() + TimeZone.setDefault(TimeZone.getTimeZone("UTC")) - result1 = FileUtils.parseName(c, false) - result2 = FileUtils.parseName(c2.replace("('|`)".toRegex(), ""), true) - // if using stat, seconds will also be available, so they won't be equal - assertNotEquals(result1.date, result2.date) - assertEquals(result1.name, result2.name) - assertEquals(result1.path, result2.path) + lsLines.forEachIndexed { index: Int, s: String -> + val result1 = FileUtils.parseName(s, false) + val result2 = FileUtils.parseName(statLines[index].replace("('|`)".toRegex(), ""), true) + assertEquals( + "Parse error at index $index.\n lsLines=[$s]\n statLines=[${statLines[index]}]\n", + result1.name, + result2.name + ) + assertEquals( + "Parse error at index $index.\n lsLines=[$s]\n statLines=[${statLines[index]}]\n", + result1.path, + result2.path + ) + } TimeZone.setDefault(systemTz) } From dbfcced4898239144644209303ab617a4aeea3f2 Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Fri, 7 Oct 2022 02:22:29 +0530 Subject: [PATCH 16/25] Bump version --- app/build.gradle | 4 ++-- app/src/main/res/values/translators.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a7a79a48d0..3cc8a4ca67 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { applicationId "com.amaze.filemanager" minSdkVersion 14 targetSdkVersion 30 - versionCode 114 - versionName "3.8.2" + versionCode 115 + versionName "3.8.3" multiDexEnabled true vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/res/values/translators.xml b/app/src/main/res/values/translators.xml index 1e810af0e7..75af81471e 100644 --- a/app/src/main/res/values/translators.xml +++ b/app/src/main/res/values/translators.xml @@ -42,7 +42,7 @@ ngoisaosang Naofumi Fukue Kuralarasi for StarsSoft - v3.8.2 + v3.8.3 Arpit Khurana Vishal Nehra Emmanuel Messulam From fad552172a51865c7102c24ddac0095f42457bc2 Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Thu, 10 Nov 2022 12:21:27 +0530 Subject: [PATCH 17/25] fix merge conflicts --- .../com/amaze/filemanager/ui/activities/MainActivity.java | 4 +++- .../ui/activities/superclasses/PreferenceActivity.java | 2 ++ .../fragments/preferencefragments/PreferencesConstants.kt | 1 + app/src/main/res/values/strings.xml | 3 ++- app/src/main/res/xml/behavior_prefs.xml | 6 ++++++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index f5eb3701ea..ff6c36469c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -400,7 +400,9 @@ public void onCreate(final Bundle savedInstanceState) { ExtensionsKt.updateAUAlias( this, !PackageUtils.Companion.appInstalledOrNot( - AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager())); + AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager()) + && !getBoolean( + PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS)); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java index 7b1d6d5ff7..44f83b2ae9 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/superclasses/PreferenceActivity.java @@ -24,6 +24,7 @@ import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_CHANGEPATHS; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_COLORED_NAVIGATION; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_COLORIZE_ICONS; +import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_ENABLE_MARQUEE_FILENAME; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_NEED_TO_SET_HOME; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_ROOTMODE; @@ -94,6 +95,7 @@ public boolean getBoolean(String key) { case PREFERENCE_TEXTEDITOR_NEWSTACK: case PREFERENCE_CHANGEPATHS: case PREFERENCE_ROOT_LEGACY_LISTING: + case PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS: defaultValue = false; break; case PREFERENCE_SHOW_FILE_SIZE: diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt index 62af3ebe4a..880ea1528e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/PreferencesConstants.kt @@ -75,6 +75,7 @@ object PreferencesConstants { const val PREFERENCE_ZIP_EXTRACT_PATH = "extractpath" const val PREFERENCE_TEXTEDITOR_NEWSTACK = "texteditor_newstack" const val PREFERENCE_DELETE_CONFIRMATION = "delete_confirmation" + const val PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS = "disable_player_intent_filters" // security_prefs.xml const val PREFERENCE_CRYPT_FINGERPRINT = "crypt_fingerprint" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27992d766e..eb6d37215f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,8 +790,9 @@ You only need to do this once, until the next time you select a new location for Secure FTP Unavailable "%s\n%s free of %s - "free of Amaze Cloud Plugin not supported with F-Droid version. Please use Play Store version of Amaze. + Disable player intent filters + Disables Amaze inbuilt media players from file chooser No app found to handle this intent. Do you have DocumentsUI installed? diff --git a/app/src/main/res/xml/behavior_prefs.xml b/app/src/main/res/xml/behavior_prefs.xml index 963a960e92..875ca5686b 100644 --- a/app/src/main/res/xml/behavior_prefs.xml +++ b/app/src/main/res/xml/behavior_prefs.xml @@ -36,6 +36,12 @@ app:key="clear_open_file" app:summary="@string/clear_open_file_summary" app:title="@string/clear_open_file" /> + Date: Tue, 27 Sep 2022 17:57:23 +0800 Subject: [PATCH 18/25] fix merge conflicts --- .../asynctasks/LoadFilesListTask.java | 74 ++++- .../filemanager/filesystem/FileProperties.kt | 37 ++- .../filemanager/filesystem/HybridFile.java | 26 +- .../filesystem/MakeDirectoryOperation.kt | 2 + .../filemanager/filesystem/Operations.java | 13 + .../filesystem/files/FileUtils.java | 1 - .../ui/activities/MainActivityViewModel.kt | 10 +- .../ui/fragments/MainFragment.java | 274 +++++++++--------- .../fragments/data/MainFragmentViewModel.kt | 3 +- .../com/amaze/filemanager/utils/GenericExt.kt | 9 + .../filemanager/utils/MainActivityHelper.java | 9 +- .../com/amaze/filemanager/utils/OTGUtil.kt | 5 +- .../filemanager/filesystem/HybridFileTest.kt | 114 +++++++- .../fileoperations/filesystem/OpenMode.java | 4 +- 14 files changed, 404 insertions(+), 177 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java index e23651b1c1..5874c6780e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedList; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,7 @@ import com.amaze.filemanager.database.UtilsHandler; import com.amaze.filemanager.fileoperations.exceptions.CloudPluginException; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; +import com.amaze.filemanager.filesystem.FileProperties; import com.amaze.filemanager.filesystem.HybridFile; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.RootHelper; @@ -53,6 +55,7 @@ import com.amaze.filemanager.ui.fragments.MainFragment; import com.amaze.filemanager.ui.fragments.data.MainFragmentViewModel; import com.amaze.filemanager.utils.DataUtils; +import com.amaze.filemanager.utils.GenericExtKt; import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.OnAsyncTaskFinished; import com.amaze.filemanager.utils.OnFileFound; @@ -60,6 +63,9 @@ import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; @@ -74,9 +80,10 @@ import jcifs.smb.SmbAuthException; import jcifs.smb.SmbException; import jcifs.smb.SmbFile; +import kotlin.collections.CollectionsKt; public class LoadFilesListTask - extends AsyncTask>> { + extends AsyncTask>> { private static final Logger LOG = LoggerFactory.getLogger(LoadFilesListTask.class); @@ -86,7 +93,7 @@ public class LoadFilesListTask private OpenMode openmode; private boolean showHiddenFiles, showThumbs; private DataUtils dataUtils = DataUtils.getInstance(); - private OnAsyncTaskFinished>> listener; + private OnAsyncTaskFinished>> listener; private boolean forceReload; public LoadFilesListTask( @@ -97,7 +104,7 @@ public LoadFilesListTask( boolean showThumbs, boolean showHiddenFiles, boolean forceReload, - OnAsyncTaskFinished>> l) { + OnAsyncTaskFinished>> l) { this.path = path; this.mainFragmentReference = new WeakReference<>(mainFragment); this.openmode = openmode; @@ -109,7 +116,8 @@ public LoadFilesListTask( } @Override - protected @Nullable Pair> doInBackground(Void... p) { + @SuppressWarnings({"PMD.NPathComplexity", "ComplexMethod", "LongMethod"}) + protected @Nullable Pair> doInBackground(Void... p) { final MainFragment mainFragment = this.mainFragmentReference.get(); final Context context = this.context.get(); @@ -139,7 +147,7 @@ public LoadFilesListTask( mainFragmentViewModel.setFolderCount(0); mainFragmentViewModel.setFileCount(0); - final ArrayList list; + final List list; switch (openmode) { case SMB: @@ -149,7 +157,7 @@ public LoadFilesListTask( if (!hFile.getPath().endsWith("/")) { hFile.setPath(hFile.getPath() + "/"); } - ArrayList smbCache = mainActivityViewModel.getFromListCache(path); + List smbCache = mainActivityViewModel.getFromListCache(path); openmode = OpenMode.SMB; if (smbCache != null && !forceReload) { list = smbCache; @@ -173,7 +181,7 @@ public LoadFilesListTask( break; case SFTP: HybridFile ftpHFile = new HybridFile(openmode, path); - ArrayList sftpCache = mainActivityViewModel.getFromListCache(path); + List sftpCache = mainActivityViewModel.getFromListCache(path); if (sftpCache != null && !forceReload) { list = sftpCache; } else { @@ -207,7 +215,7 @@ public LoadFilesListTask( openmode = OpenMode.OTG; break; case DOCUMENT_FILE: - ArrayList cache = mainActivityViewModel.getFromListCache(path); + List cache = mainActivityViewModel.getFromListCache(path); if (cache != null && !forceReload) { list = cache; } else { @@ -225,8 +233,7 @@ public LoadFilesListTask( case BOX: case GDRIVE: case ONEDRIVE: - ArrayList cloudCache = - mainActivityViewModel.getFromListCache(path); + List cloudCache = mainActivityViewModel.getFromListCache(path); if (cloudCache != null && !forceReload) { list = cloudCache; } else { @@ -250,10 +257,12 @@ public LoadFilesListTask( } } break; + case ANDROID_DATA: + list = listAppDataDirectories(path); + break; default: // we're neither in OTG not in SMB, load the list based on root/general filesystem - ArrayList localCache = - mainActivityViewModel.getFromListCache(path); + List localCache = mainActivityViewModel.getFromListCache(path); openmode = ListFilesCommand.INSTANCE.getOpenMode( path, mainFragment.getMainActivity().isRootExplorer()); @@ -324,13 +333,13 @@ protected void onCancelled() { } @Override - protected void onPostExecute(@Nullable Pair> list) { + protected void onPostExecute(@Nullable Pair> list) { listener.onAsyncTaskFinished(list); } - private ArrayList getCachedMediaList( + private List getCachedMediaList( MainActivityViewModel mainActivityViewModel) throws IllegalStateException { - ArrayList list; + List list; int mediaType = Integer.parseInt(path); if (5 == mediaType || 6 == mediaType @@ -635,6 +644,41 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return recentFiles; } + private @NonNull List listAppDataDirectories(@NonNull String basePath) { + if (!GenericExtKt.containsPath(FileProperties.ANDROID_DEVICE_DATA_DIRS, basePath)) { + throw new IllegalArgumentException("Invalid base path: [" + basePath + "]"); + } + Context ctx = context.get(); + @Nullable PackageManager pm = ctx != null ? ctx.getPackageManager() : null; + List retval = new ArrayList<>(); + if (pm != null) { + Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER); + for (ResolveInfo app : + CollectionsKt.distinctBy( + pm.queryIntentActivities(intent, 0), + resolveInfo -> resolveInfo.activityInfo.packageName)) { + File dir = new File(new File(basePath), app.activityInfo.packageName); + if (dir.exists()) { + LayoutElementParcelable element = + new LayoutElementParcelable( + ctx, + dir.getAbsolutePath(), + "", + "", + Long.toString(dir.length()), + dir.length(), + false, + Long.toString(dir.lastModified()), + true, + false, + OpenMode.ANDROID_DATA); + retval.add(element); + } + } + } + return retval; + } + /** * Lists files from an OTG device * diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt b/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt index 90b4d4df11..ed6f8128f5 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/FileProperties.kt @@ -21,6 +21,7 @@ package com.amaze.filemanager.filesystem import android.app.usage.StorageStatsManager +import android.content.ContentResolver.SCHEME_CONTENT import android.content.Context import android.net.Uri import android.os.Build @@ -35,6 +36,7 @@ import com.amaze.filemanager.filesystem.ExternalSdCardOperation.isOnExtSdCard import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.smb.CifsContexts import com.amaze.filemanager.utils.OTGUtil +import com.amaze.filemanager.utils.containsPath import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File @@ -53,11 +55,17 @@ object FileProperties { private const val COM_ANDROID_EXTERNALSTORAGE_DOCUMENTS = "com.android.externalstorage.documents" - val EXCLUDED_DIRS = arrayOf( - File(Environment.getExternalStorageDirectory(), "Android/data").absolutePath, - File(Environment.getExternalStorageDirectory(), "Android/obb").absolutePath + @JvmField + val ANDROID_DATA_DIRS = arrayOf( + "Android/data", + "Android/obb" ) + @JvmField + val ANDROID_DEVICE_DATA_DIRS = ANDROID_DATA_DIRS.map { + File(Environment.getExternalStorageDirectory(), it).absolutePath + } + /** * Check if a file is readable. * @@ -214,18 +222,27 @@ object FileProperties { @JvmStatic fun unmapPathForApi30OrAbove(uriPath: String): String? { - val uri = Uri.parse(uriPath) - return uri.path?.let { p -> - File( - Environment.getExternalStorageDirectory(), - p.substringAfter("tree/primary:") - ).absolutePath + return if (uriPath.startsWith(SCHEME_CONTENT)) { + val uri = Uri.parse(uriPath) + return uri.path?.let { p -> + File( + Environment.getExternalStorageDirectory(), + p.substringAfter("tree/primary:") + ).absolutePath + } + } else { + uriPath } } @JvmStatic fun remapPathForApi30OrAbove(path: String, openDocumentTree: Boolean = false): String { - return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && EXCLUDED_DIRS.contains(path)) { + return if (ANDROID_DEVICE_DATA_DIRS.containsPath(path)) { + path + } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && ANDROID_DEVICE_DATA_DIRS.any { + path.startsWith(it) && path != it + } + ) { val suffix = path.substringAfter(Environment.getExternalStorageDirectory().absolutePath) val documentId = "$STORAGE_PRIMARY:${suffix.substring(1)}" diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java index c0cf400889..fe3bbb020c 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java @@ -20,6 +20,7 @@ package com.amaze.filemanager.filesystem; +import static com.amaze.filemanager.filesystem.FileProperties.ANDROID_DATA_DIRS; import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTPS_URI_PREFIX; import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_URI_PREFIX; import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.EnumSet; +import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -107,7 +109,9 @@ import io.reactivex.schedulers.Schedulers; import jcifs.smb.SmbException; import jcifs.smb.SmbFile; +import kotlin.collections.ArraysKt; import kotlin.io.ByteStreamsKt; +import kotlin.text.Charsets; import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.common.Buffer; import net.schmizz.sshj.common.IOUtils; @@ -260,6 +264,10 @@ public boolean isGoogleDriveFile() { return mode == OpenMode.GDRIVE; } + public boolean isAndroidDataDir() { + return mode == OpenMode.ANDROID_DATA; + } + public boolean isCloudDriveFile() { return isBoxFile() || isDropBoxFile() || isOneDriveFile() || isGoogleDriveFile(); } @@ -537,6 +545,22 @@ public String getParent(Context context) { case ROOT: return getFile().getParent(); case SFTP: + case DOCUMENT_FILE: + String thisPath = path; + if (thisPath.contains("%")) { + try { + thisPath = URLDecoder.decode(path, Charsets.UTF_8.name()); + } catch (UnsupportedEncodingException ignored) { + } + } + List pathSegments = Uri.parse(thisPath).getPathSegments(); + String currentName = pathSegments.get(pathSegments.size() - 1); + String parent = thisPath.substring(0, thisPath.lastIndexOf(currentName)); + if (ArraysKt.any(ANDROID_DATA_DIRS, dir -> parent.endsWith(dir + "/"))) { + return FileProperties.unmapPathForApi30OrAbove(parent); + } else { + return parent; + } default: if (getPath().length() == getName(context).length()) { return null; @@ -1435,7 +1459,7 @@ public Boolean executeWithFtpClient(@NonNull FTPClient ftpClient) throws IOExcep } catch (Exception e) { LOG.warn("failed to create folder for cloud file", e); } - } else MakeDirectoryOperation.mkdir(getFile(), context); + } else MakeDirectoryOperation.mkdirs(context, this); } public boolean delete(Context context, boolean rootmode) diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt b/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt index 5a7de4a18b..5d44a4a697 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/MakeDirectoryOperation.kt @@ -93,6 +93,8 @@ object MakeDirectoryOperation { isSuccessful = documentFile != null } OpenMode.FILE -> isSuccessful = mkdir(File(file.getPath()), context) + // With ANDROID_DATA will not accept create directory + OpenMode.ANDROID_DATA -> isSuccessful = false else -> isSuccessful = true } return isSuccessful diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java b/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java index 4e17a514d6..e2fe38b68b 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/Operations.java @@ -149,6 +149,12 @@ protected Void doInBackground(Void... params) { return null; } + // Android data directory, prohibit create directory + if (file.isAndroidDataDir()) { + errorCallBack.done(file, false); + return null; + } + if (file.isSftp() || file.isFtp()) { file.mkdir(context); /* @@ -299,6 +305,13 @@ protected Void doInBackground(Void... params) { errorCallBack.exists(file); return null; } + + // Android data directory, prohibit create file + if (file.isAndroidDataDir()) { + errorCallBack.done(file, false); + return null; + } + if (file.isSftp() || file.isFtp()) { OutputStream out = file.getOutputStream(context); if (out == null) { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java index 553903cc4b..a137e6e893 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/FileUtils.java @@ -599,7 +599,6 @@ public static void openWith( /** Method determines if there is something to go back to */ public static boolean canGoBack(Context context, HybridFile currentFile) { switch (currentFile.getMode()) { - // we're on main thread and can't list the cloud files case DROPBOX: case BOX: diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt index 6b78722385..dc01bebcfd 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivityViewModel.kt @@ -31,8 +31,8 @@ import kotlinx.coroutines.launch class MainActivityViewModel(val applicationContext: Application) : AndroidViewModel(applicationContext) { - var mediaCacheHash: List?> = List(5) { null } - var listCache: LruCache> = LruCache(50) + var mediaCacheHash: List?> = List(5) { null } + var listCache: LruCache> = LruCache(50) companion object { /** @@ -44,7 +44,7 @@ class MainActivityViewModel(val applicationContext: Application) : /** * Put list for a given path in cache */ - fun putInCache(path: String, listToCache: ArrayList) { + fun putInCache(path: String, listToCache: List) { viewModelScope.launch(Dispatchers.Default) { listCache.put(path, listToCache) } @@ -62,14 +62,14 @@ class MainActivityViewModel(val applicationContext: Application) : /** * Get cache from a given path and updates files / folder count */ - fun getFromListCache(path: String): ArrayList? { + fun getFromListCache(path: String): List? { return listCache.get(path) } /** * Get cache from a given path and updates files / folder count */ - fun getFromMediaFilesCache(mediaType: Int): ArrayList? { + fun getFromMediaFilesCache(mediaType: Int): List? { return mediaCacheHash[mediaType] } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index 5e6b6e4d67..f02a3a1ffd 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -24,6 +24,8 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.Q; +import static com.amaze.filemanager.filesystem.FileProperties.ANDROID_DATA_DIRS; +import static com.amaze.filemanager.filesystem.FileProperties.ANDROID_DEVICE_DATA_DIRS; import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_DIVIDERS; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_GOBACK_BUTTON; @@ -63,6 +65,7 @@ import com.amaze.filemanager.filesystem.files.FileListSorter; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.ui.ExtensionsKt; +import com.amaze.filemanager.ui.ExtensionsKt; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.activities.MainActivityViewModel; import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; @@ -79,6 +82,7 @@ import com.amaze.filemanager.ui.views.WarnableTextInputValidator; import com.amaze.filemanager.utils.BottomBarButtonPath; import com.amaze.filemanager.utils.DataUtils; +import com.amaze.filemanager.utils.GenericExtKt; import com.amaze.filemanager.utils.MainActivityHelper; import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.Utils; @@ -90,13 +94,11 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.UriPermission; -import android.content.res.Resources; import android.graphics.Color; import android.media.RingtoneManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Environment; import android.provider.DocumentsContract; import android.text.TextUtils; import android.text.format.Formatter; @@ -131,6 +133,8 @@ import jcifs.smb.SmbException; import jcifs.smb.SmbFile; +import kotlin.collections.ArraysKt; +import kotlin.collections.CollectionsKt; public class MainFragment extends Fragment implements BottomBarButtonPath, @@ -142,7 +146,6 @@ public class MainFragment extends Fragment public RecyclerAdapter adapter; private SharedPreferences sharedPref; - private Resources res; // ATTRIBUTES FOR APPEARANCE AND COLORS private LinearLayoutManager mLayoutManager; @@ -164,7 +167,7 @@ public class MainFragment extends Fragment private MainFragmentViewModel mainFragmentViewModel; private MainActivityViewModel mainActivityViewModel; - private ActivityResultLauncher handleDocumentUriForRestrictedDirectories = + private final ActivityResultLauncher handleDocumentUriForRestrictedDirectories = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -191,19 +194,18 @@ public void onCreate(Bundle savedInstanceState) { mainActivityViewModel = new ViewModelProvider(requireMainActivity()).get(MainActivityViewModel.class); - utilsProvider = getMainActivity().getUtilsProvider(); + utilsProvider = requireMainActivity().getUtilsProvider(); sharedPref = PreferenceManager.getDefaultSharedPreferences(requireActivity()); - res = getResources(); mainFragmentViewModel.initBundleArguments(getArguments()); mainFragmentViewModel.initIsList(); mainFragmentViewModel.initColumns(sharedPref); mainFragmentViewModel.initSortModes( SortHandler.getSortType(getContext(), getCurrentPath()), sharedPref); - mainFragmentViewModel.setAccentColor(getMainActivity().getAccent()); + mainFragmentViewModel.setAccentColor(requireMainActivity().getAccent()); mainFragmentViewModel.setPrimaryColor( - getMainActivity().getCurrentColorPreference().getPrimaryFirstTab()); + requireMainActivity().getCurrentColorPreference().getPrimaryFirstTab()); mainFragmentViewModel.setPrimaryTwoColor( - getMainActivity().getCurrentColorPreference().getPrimarySecondTab()); + requireMainActivity().getCurrentColorPreference().getPrimarySecondTab()); } @Override @@ -219,7 +221,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); mainFragmentViewModel = new ViewModelProvider(this).get(MainFragmentViewModel.class); listView = rootView.findViewById(R.id.listView); - mToolbarContainer = getMainActivity().getAppbar().getAppbarLayout(); + mToolbarContainer = requireMainActivity().getAppbar().getAppbarLayout(); fastScroller = rootView.findViewById(R.id.fastscroll); fastScroller.setPressedHandleColor(mainFragmentViewModel.getAccentColor()); View.OnTouchListener onTouchListener = @@ -275,7 +277,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat // View footerView = getActivity().getLayoutInflater().inflate(R.layout.divider, null);// TODO: // 23/5/2017 use or delete dividerItemDecoration = - new DividerItemDecoration(getActivity(), false, getBoolean(PREFERENCE_SHOW_DIVIDERS)); + new DividerItemDecoration(requireActivity(), false, getBoolean(PREFERENCE_SHOW_DIVIDERS)); listView.addItemDecoration(dividerItemDecoration); mSwipeRefreshLayout.setColorSchemeColors(mainFragmentViewModel.getAccentColor()); DefaultItemAnimator animator = new DefaultItemAnimator(); @@ -404,7 +406,7 @@ public void onReceive(Context context, Intent intent) { && mainFragmentViewModel.getEncryptBaseFile() != null) { FileUtils.openFile( mainFragmentViewModel.getEncryptBaseFile().getFile(), - getMainActivity(), + requireMainActivity(), sharedPref); mainFragmentViewModel.setEncryptOpen(false); } @@ -431,14 +433,14 @@ public void onListItemClicked( if (mainFragmentViewModel.getResults()) { // check to initialize search results // if search task is been running, cancel it - FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); + FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); SearchWorkerFragment fragment = (SearchWorkerFragment) fragmentManager.findFragmentByTag(MainActivity.TAG_ASYNC_HELPER); if (fragment != null) { if (fragment.searchAsyncTask.getStatus() == AsyncTask.Status.RUNNING) { fragment.searchAsyncTask.cancel(true); } - getActivity().getSupportFragmentManager().beginTransaction().remove(fragment).commit(); + requireActivity().getSupportFragmentManager().beginTransaction().remove(fragment).commit(); } mainFragmentViewModel.setRetainSearchTask(true); @@ -448,13 +450,13 @@ public void onListItemClicked( MainActivityHelper.SEARCH_TEXT = null; } - if (getMainActivity().getListItemSelected()) { + if (requireMainActivity().getListItemSelected()) { if (isBackButton) { - getMainActivity().setListItemSelected(false); - if (getMainActivity().getActionModeHelper().getActionMode() != null) { - getMainActivity().getActionModeHelper().getActionMode().finish(); + requireMainActivity().setListItemSelected(false); + if (requireMainActivity().getActionModeHelper().getActionMode() != null) { + requireMainActivity().getActionModeHelper().getActionMode().finish(); } - getMainActivity().getActionModeHelper().setActionMode(null); + requireMainActivity().getActionModeHelper().setActionMode(null); } else { // the first {goback} item if back navigation is enabled adapter.toggleChecked(position, imageView); @@ -464,8 +466,8 @@ public void onListItemClicked( goBackItemClick(); } else { // hiding search view if visible - if (getMainActivity().getAppbar().getSearchView().isEnabled()) { - getMainActivity().getAppbar().getSearchView().hideSearchView(); + if (requireMainActivity().getAppbar().getSearchView().isEnabled()) { + requireMainActivity().getAppbar().getSearchView().hideSearchView(); } String path = @@ -523,9 +525,9 @@ public void updateTabWithDb(Tab tab) { */ public void returnIntentResults(HybridFileParcelable baseFile) { - getMainActivity().mReturnIntent = false; + requireMainActivity().mReturnIntent = false; - Uri mediaStoreUri = Utils.getUriForBaseFile(getActivity(), baseFile); + Uri mediaStoreUri = Utils.getUriForBaseFile(requireActivity(), baseFile); LOG.debug( mediaStoreUri.toString() + "\t" @@ -534,7 +536,7 @@ public void returnIntentResults(HybridFileParcelable baseFile) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setAction(Intent.ACTION_SEND); - if (getMainActivity().mRingtonePickerIntent) { + if (requireMainActivity().mRingtonePickerIntent) { intent.setDataAndType( mediaStoreUri, MimeTypes.getMimeType(baseFile.getPath(), baseFile.isDirectory())); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, mediaStoreUri); @@ -582,12 +584,13 @@ && getMainActivity().getActionModeHelper().getActionMode() != null) { OpenMode openMode = providedOpenMode; String actualPath = FileProperties.remapPathForApi30OrAbove(providedPath, false); - if (!providedPath.equals(actualPath) && SDK_INT >= Q) { - openMode = loadPathInQ(actualPath, providedPath); + if (SDK_INT >= Q && ArraysKt.any(ANDROID_DATA_DIRS, providedPath::contains)) { + openMode = loadPathInQ(actualPath, providedPath, providedOpenMode); } // Monkeypatch :( to fix problems with unexpected non content URI path while openMode is still // OpenMode.DOCUMENT_FILE - else if (actualPath.startsWith("/") && OpenMode.DOCUMENT_FILE.equals(openMode)) { + else if (actualPath.startsWith("/") + && (OpenMode.DOCUMENT_FILE.equals(openMode) || OpenMode.ANDROID_DATA.equals(openMode))) { openMode = OpenMode.FILE; } @@ -616,52 +619,61 @@ else if (actualPath.startsWith("/") && OpenMode.DOCUMENT_FILE.equals(openMode)) } @RequiresApi(api = Q) - private OpenMode loadPathInQ(String actualPath, String providedPath) { - - boolean hasAccessToSpecialFolder = false; - List uriPermissions = - getContext().getContentResolver().getPersistedUriPermissions(); - - if (uriPermissions != null && uriPermissions.size() > 0) { - for (UriPermission p : uriPermissions) { - if (p.isReadPermission() && actualPath.startsWith(p.getUri().toString())) { - hasAccessToSpecialFolder = true; - SafRootHolder.setUriRoot(p.getUri()); - break; - } - } - } + private OpenMode loadPathInQ(String actualPath, String providedPath, OpenMode providedMode) { - if (!hasAccessToSpecialFolder) { - Intent intent = - new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - .putExtra( - DocumentsContract.EXTRA_INITIAL_URI, - Uri.parse(FileProperties.remapPathForApi30OrAbove(providedPath, true))); - MaterialDialog d = - GeneralDialogCreation.showBasicDialog( - getMainActivity(), - R.string.android_data_prompt_saf_access, - R.string.android_data_prompt_saf_access_title, - android.R.string.ok, - android.R.string.cancel); - d.getActionButton(DialogAction.POSITIVE) - .setOnClickListener( - v -> { - ExtensionsKt.runIfDocumentsUIExists( - intent, - getMainActivity(), - () -> handleDocumentUriForRestrictedDirectories.launch(intent)); - - d.dismiss(); - }); - d.show(); - // At this point LoadFilesListTask will be triggered. - // No harm even give OpenMode.FILE here, it loads blank when it doesn't; and after the - // UriPermission is granted loadlist will be called again + if (GenericExtKt.containsPath(ANDROID_DEVICE_DATA_DIRS, providedPath) + && !OpenMode.ANDROID_DATA.equals(providedMode)) { + return OpenMode.ANDROID_DATA; + } else if (actualPath.startsWith("/")) { return OpenMode.FILE; + } else if (actualPath.equals(providedPath)) { + return providedMode; } else { - return OpenMode.DOCUMENT_FILE; + boolean hasAccessToSpecialFolder = false; + List uriPermissions = + requireContext().getContentResolver().getPersistedUriPermissions(); + + if (uriPermissions != null && uriPermissions.size() > 0) { + for (UriPermission p : uriPermissions) { + if (p.isReadPermission() && actualPath.startsWith(p.getUri().toString())) { + hasAccessToSpecialFolder = true; + SafRootHolder.setUriRoot(p.getUri()); + break; + } + } + } + + if (!hasAccessToSpecialFolder) { + Intent intent = + new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + .putExtra( + DocumentsContract.EXTRA_INITIAL_URI, + Uri.parse(FileProperties.remapPathForApi30OrAbove(providedPath, true))); + MaterialDialog d = + GeneralDialogCreation.showBasicDialog( + requireMainActivity(), + R.string.android_data_prompt_saf_access, + R.string.android_data_prompt_saf_access_title, + android.R.string.ok, + android.R.string.cancel); + d.getActionButton(DialogAction.POSITIVE) + .setOnClickListener( + v -> { + ExtensionsKt.runIfDocumentsUIExists( + intent, + requireMainActivity(), + () -> handleDocumentUriForRestrictedDirectories.launch(intent)); + + d.dismiss(); + }); + d.show(); + // At this point LoadFilesListTask will be triggered. + // No harm even give OpenMode.FILE here, it loads blank when it doesn't; and after the + // UriPermission is granted loadlist will be called again + return OpenMode.FILE; + } else { + return OpenMode.DOCUMENT_FILE; + } } } @@ -683,9 +695,9 @@ void initNoFileLayout() { (v, keyCode, event) -> { if (event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) { - getMainActivity().getFAB().requestFocus(); + requireMainActivity().getFAB().requestFocus(); } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - getMainActivity().onBackPressed(); + requireMainActivity().onBackPressed(); } else { return false; } @@ -715,7 +727,7 @@ void initNoFileLayout() { * @param grid whether to set grid view or list view */ public void setListElements( - ArrayList bitmap, + List bitmap, boolean back, String path, final OpenMode openMode, @@ -776,7 +788,7 @@ public void reloadListElements(boolean back, boolean results, boolean grid) { adapter = new RecyclerAdapter( - getMainActivity(), + requireMainActivity(), this, utilsProvider, sharedPref, @@ -803,7 +815,8 @@ public void reloadListElements(boolean back, boolean results, boolean grid) { if (mainFragmentViewModel.getAddHeader() && mainFragmentViewModel.isList()) { dividerItemDecoration = - new DividerItemDecoration(getActivity(), true, getBoolean(PREFERENCE_SHOW_DIVIDERS)); + new DividerItemDecoration( + requireMainActivity(), true, getBoolean(PREFERENCE_SHOW_DIVIDERS)); listView.addItemDecoration(dividerItemDecoration); mainFragmentViewModel.setAddHeader(false); } @@ -818,9 +831,9 @@ public void reloadListElements(boolean back, boolean results, boolean grid) { } } - getMainActivity().updatePaths(mainFragmentViewModel.getNo()); - getMainActivity().showFab(); - getMainActivity().getAppbar().getAppbarLayout().setExpanded(true); + requireMainActivity().updatePaths(mainFragmentViewModel.getNo()); + requireMainActivity().showFab(); + requireMainActivity().getAppbar().getAppbarLayout().setExpanded(true); listView.stopScroll(); fastScroller.setRecyclerView( listView, @@ -885,13 +898,13 @@ private LayoutElementParcelable getBackElement() { */ private void resumeDecryptOperations() { if (SDK_INT >= JELLY_BEAN_MR2) { - (getActivity()) + (requireMainActivity()) .registerReceiver( decryptReceiver, new IntentFilter(EncryptDecryptUtils.DECRYPT_BROADCAST)); if (!mainFragmentViewModel.isEncryptOpen() && !Utils.isNullOrEmpty(mainFragmentViewModel.getEncryptBaseFiles())) { // we've opened the file and are ready to delete it - new DeleteTask(getActivity()).execute(mainFragmentViewModel.getEncryptBaseFiles()); + new DeleteTask(requireMainActivity()).execute(mainFragmentViewModel.getEncryptBaseFiles()); mainFragmentViewModel.setEncryptBaseFiles(new ArrayList<>()); } } @@ -1008,7 +1021,7 @@ public void goBack() { if (!mainFragmentViewModel.getResults()) { if (!mainFragmentViewModel.getRetainSearchTask()) { // normal case - if (getMainActivity().getListItemSelected()) { + if (requireMainActivity().getListItemSelected()) { adapter.toggleChecked(false); } else { if (OpenMode.SMB.equals(mainFragmentViewModel.getOpenMode())) { @@ -1061,41 +1074,27 @@ public void goBack() { && mainFragmentViewModel.getHome().equals(mainFragmentViewModel.getCurrentPath())) || mainFragmentViewModel.getIsOnCloudRoot()) { getMainActivity().exit(); - } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode())) { - if (!currentFile.getPath().startsWith("content://")) { - mainFragmentViewModel.setOpenMode(OpenMode.FILE); - currentFile.setMode(OpenMode.FILE); - currentFile.setPath(Environment.getExternalStorageDirectory().getAbsolutePath()); - loadlist(currentFile.getPath(), false, mainFragmentViewModel.getOpenMode(), false); + } else if (OpenMode.DOCUMENT_FILE.equals(mainFragmentViewModel.getOpenMode()) + && !currentFile.getPath().startsWith("content://")) { + if (CollectionsKt.contains( + ANDROID_DEVICE_DATA_DIRS, currentFile.getParent(getContext()))) { + loadlist(currentFile.getParent(getContext()), false, OpenMode.ANDROID_DATA, false); } else { - List pathSegments = Uri.parse(currentFile.getPath()).getPathSegments(); - if (pathSegments.size() < 3) { - mainFragmentViewModel.setOpenMode(OpenMode.FILE); - String subPath = pathSegments.get(1); - currentFile.setMode(OpenMode.FILE); - currentFile.setPath( - new File( - Environment.getExternalStorageDirectory(), - subPath.substring( - subPath.lastIndexOf(':') + 1, subPath.lastIndexOf('/'))) - .getAbsolutePath()); - loadlist(currentFile.getPath(), false, mainFragmentViewModel.getOpenMode(), false); - } else { - loadlist( - currentFile.getParent(getContext()), - true, - mainFragmentViewModel.getOpenMode(), - false); - } + loadlist( + currentFile.getParent(getContext()), + true, + mainFragmentViewModel.getOpenMode(), + false); } - } else if (FileUtils.canGoBack(getContext(), currentFile)) { loadlist( currentFile.getParent(getContext()), true, mainFragmentViewModel.getOpenMode(), false); - } else getMainActivity().exit(); + } else { + requireMainActivity().exit(); + } } } else { // case when we had pressed on an item from search results and wanna go back @@ -1104,7 +1103,7 @@ public void goBack() { if (MainActivityHelper.SEARCH_TEXT != null) { // starting the search query again :O - FragmentManager fm = getMainActivity().getSupportFragmentManager(); + FragmentManager fm = requireMainActivity().getSupportFragmentManager(); // getting parent path to resume search from there String parentPath = @@ -1121,7 +1120,7 @@ public void goBack() { parentPath, MainActivityHelper.SEARCH_TEXT, mainFragmentViewModel.getOpenMode(), - getMainActivity().isRootExplorer(), + requireMainActivity().isRootExplorer(), sharedPref.getBoolean(SearchWorkerFragment.KEY_REGEX, false), sharedPref.getBoolean(SearchWorkerFragment.KEY_REGEX_MATCHES, false)); } else { @@ -1131,7 +1130,7 @@ public void goBack() { } } else { // to go back after search list have been popped - FragmentManager fm = getActivity().getSupportFragmentManager(); + FragmentManager fm = requireMainActivity().getSupportFragmentManager(); SearchWorkerFragment fragment = (SearchWorkerFragment) fm.findFragmentByTag(MainActivity.TAG_ASYNC_HELPER); if (fragment != null) { @@ -1153,7 +1152,7 @@ public void goBack() { public void reauthenticateSmb() { if (mainFragmentViewModel.getSmbPath() != null) { try { - getMainActivity() + requireMainActivity() .runOnUiThread( () -> { int i; @@ -1162,7 +1161,7 @@ public void reauthenticateSmb() { DataUtils.getInstance() .containsServer(mainFragmentViewModel.getSmbPath())) != -1) { - getMainActivity() + requireMainActivity() .showSMBDialog( DataUtils.getInstance().getServers().get(i)[0], mainFragmentViewModel.getSmbPath(), @@ -1183,7 +1182,7 @@ public void goBackItemClick() { HybridFile currentFile = new HybridFile(mainFragmentViewModel.getOpenMode(), mainFragmentViewModel.getCurrentPath()); if (!mainFragmentViewModel.getResults()) { - if (getMainActivity().getListItemSelected()) { + if (requireMainActivity().getListItemSelected()) { adapter.toggleChecked(false); } else { if (mainFragmentViewModel.getOpenMode() == OpenMode.SMB) { @@ -1201,14 +1200,14 @@ public void goBackItemClick() { } else loadlist(mainFragmentViewModel.getHome(), false, OpenMode.FILE, false); } else if (("/").equals(mainFragmentViewModel.getCurrentPath()) || mainFragmentViewModel.getIsOnCloudRoot()) { - getMainActivity().exit(); + requireMainActivity().exit(); } else if (FileUtils.canGoBack(getContext(), currentFile)) { loadlist( currentFile.getParent(getContext()), true, mainFragmentViewModel.getOpenMode(), false); - } else getMainActivity().exit(); + } else requireMainActivity().exit(); } } else { loadlist(currentFile.getPath(), true, mainFragmentViewModel.getOpenMode(), false); @@ -1227,7 +1226,7 @@ public void updateList(boolean forceReload) { @Override public void onResume() { super.onResume(); - (getActivity()) + (requireActivity()) .registerReceiver(receiver2, new IntentFilter(MainActivity.KEY_INTENT_LOAD_LIST)); resumeDecryptOperations(); @@ -1237,13 +1236,13 @@ public void onResume() { @Override public void onPause() { super.onPause(); - (getActivity()).unregisterReceiver(receiver2); + (requireActivity()).unregisterReceiver(receiver2); if (customFileObserver != null) { customFileObserver.stopWatching(); } if (SDK_INT >= JELLY_BEAN_MR2) { - (getActivity()).unregisterReceiver(decryptReceiver); + (requireActivity()).unregisterReceiver(decryptReceiver); } } @@ -1276,7 +1275,7 @@ public ArrayList addToSmb( LayoutElementParcelable layoutElement = new LayoutElementParcelable( - getContext(), + requireContext(), name, aMFilePathBuilder.build().toString(), "", @@ -1296,7 +1295,7 @@ public ArrayList addToSmb( mainFragmentViewModel.setFileCount(mainFragmentViewModel.getFileCount() + 1); LayoutElementParcelable layoutElement = new LayoutElementParcelable( - getContext(), + requireContext(), name, aMFile.getPath(), "", @@ -1326,7 +1325,7 @@ private LayoutElementParcelable addTo(@NonNull HybridFileParcelable hybridFilePa if (hybridFileParcelable.isDirectory()) { LayoutElementParcelable layoutElement = new LayoutElementParcelable( - getContext(), + requireContext(), hybridFileParcelable.getPath(), hybridFileParcelable.getPermission(), hybridFileParcelable.getLink(), @@ -1383,7 +1382,7 @@ public void hide(String path) { File f1 = new File(path + "/" + ".nomedia"); if (!f1.exists()) { try { - getMainActivity() + requireMainActivity() .mainActivityHelper .mkFile( new HybridFile(OpenMode.FILE, path), @@ -1393,16 +1392,17 @@ public void hide(String path) { LOG.warn("failure when hiding file", e); } } - FileUtils.scanFile(getActivity(), new HybridFile[] {new HybridFile(OpenMode.FILE, path)}); + FileUtils.scanFile( + requireMainActivity(), new HybridFile[] {new HybridFile(OpenMode.FILE, path)}); } } public void addShortcut(LayoutElementParcelable path) { // Adding shortcut for MainActivity // on Home screen - final Context ctx = getContext(); + final Context ctx = requireContext(); - if (!ShortcutManagerCompat.isRequestPinShortcutSupported(ctx)) { + if (!ShortcutManagerCompat.isRequestPinShortcutSupported(requireContext())) { Toast.makeText( getActivity(), getString(R.string.add_shortcut_not_supported_by_launcher), @@ -1419,7 +1419,7 @@ public void addShortcut(LayoutElementParcelable path) { // Using file path as shortcut id. ShortcutInfoCompat info = new ShortcutInfoCompat.Builder(ctx, path.desc) - .setActivity(getMainActivity().getComponentName()) + .setActivity(requireMainActivity().getComponentName()) .setIcon(IconCompat.createWithResource(ctx, R.mipmap.ic_launcher)) .setIntent(shortcutIntent) .setLongLabel(path.desc) @@ -1431,8 +1431,8 @@ public void addShortcut(LayoutElementParcelable path) { // This method is used to implement the modification for the pre Searching public void onSearchPreExecute(String query) { - getMainActivity().getAppbar().getBottomBar().setPathText(""); - getMainActivity() + requireMainActivity().getAppbar().getBottomBar().setPathText(""); + requireMainActivity() .getAppbar() .getBottomBar() .setFullPathText(getString(R.string.searching, query)); @@ -1454,19 +1454,19 @@ public void addSearchResult(@NonNull HybridFileParcelable hybridFileParcelable, // adding new value to LIST_ELEMENTS @Nullable LayoutElementParcelable layoutElementAdded = addTo(hybridFileParcelable); - if (!getMainActivity() + if (!requireMainActivity() .getAppbar() .getBottomBar() .getFullPathText() .contains(getString(R.string.searching))) { - getMainActivity() + requireMainActivity() .getAppbar() .getBottomBar() .setFullPathText(getString(R.string.searching, query)); } if (!mainFragmentViewModel.getResults()) { reloadListElements(false, true, !mainFragmentViewModel.isList()); - getMainActivity().getAppbar().getBottomBar().setPathText(""); + requireMainActivity().getAppbar().getBottomBar().setPathText(""); } else if (layoutElementAdded != null) { adapter.addItem(layoutElementAdded); } @@ -1474,7 +1474,7 @@ public void addSearchResult(@NonNull HybridFileParcelable hybridFileParcelable, } public void onSearchCompleted(final String query) { - final ArrayList elements = mainFragmentViewModel.getListElements(); + final List elements = mainFragmentViewModel.getListElements(); if (!mainFragmentViewModel.getResults()) { // no results were found mainFragmentViewModel.getListElements().clear(); @@ -1506,7 +1506,7 @@ public MainActivity requireMainActivity() { } @Nullable - public ArrayList getElementsList() { + public List getElementsList() { return mainFragmentViewModel.getListElements(); } @@ -1577,7 +1577,7 @@ public int getRootDrawable() { } private boolean getBoolean(String key) { - return getMainActivity().getBoolean(key); + return requireMainActivity().getBoolean(key); } @Override @@ -1638,10 +1638,10 @@ public void adjustListViewForTv( int[] location = new int[2]; viewHolder.baseItemView.getLocationOnScreen(location); LOG.info("Current x and y " + location[0] + " " + location[1]); - if (location[1] < getMainActivity().getAppbar().getAppbarLayout().getHeight()) { + if (location[1] < requireMainActivity().getAppbar().getAppbarLayout().getHeight()) { listView.scrollToPosition(Math.max(viewHolder.getAdapterPosition() - 5, 0)); } else if (location[1] + viewHolder.baseItemView.getHeight() - > getContext().getResources().getDisplayMetrics().heightPixels) { + > requireContext().getResources().getDisplayMetrics().heightPixels) { listView.scrollToPosition( Math.min(viewHolder.getAdapterPosition() + 5, adapter.getItemCount() - 1)); } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt index 2e50b0f713..33cad7f3a1 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/data/MainFragmentViewModel.kt @@ -38,14 +38,13 @@ import com.amaze.filemanager.utils.DataUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.util.* -import kotlin.collections.ArrayList class MainFragmentViewModel : ViewModel() { var currentPath: String? = null /** This is not an exact copy of the elements in the adapter */ - var listElements = ArrayList() + var listElements: List = ArrayList() var adapterListItems: ArrayList? = null var iconList: ArrayList? = null diff --git a/app/src/main/java/com/amaze/filemanager/utils/GenericExt.kt b/app/src/main/java/com/amaze/filemanager/utils/GenericExt.kt index 19050c73f4..42d655d5e0 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/GenericExt.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/GenericExt.kt @@ -84,6 +84,15 @@ fun ByteArray.toHex(separatorStr: String = ""): String = "%02x".format(eachByte) } +/** + * Test a [List] for given path. Assumed paths in the list are not ending with /, so check for + * both ended with or not ended with / with the given path parameter. + */ +fun List<*>.containsPath(path: String): Boolean { + return this.contains(path) || + (path.endsWith('/') && this.contains(path.substringBeforeLast('/'))) +} + interface Function { fun apply(t: T): R } diff --git a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java index ce03fd7aea..85996942b3 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -166,9 +166,14 @@ public void mkdir(final OpenMode openMode, final String path, final MainFragment "", (dialog, which) -> { EditText textfield = dialog.getCustomView().findViewById(R.id.singleedittext_input); + String parentPath = path; + if (OpenMode.DOCUMENT_FILE.equals(openMode) + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + parentPath = FileProperties.remapPathForApi30OrAbove(path, false); + } mkDir( - new HybridFile(openMode, path), - new HybridFile(openMode, path, textfield.getText().toString().trim(), true), + new HybridFile(openMode, parentPath), + new HybridFile(openMode, parentPath, textfield.getText().toString().trim(), true), ma); dialog.dismiss(); }, diff --git a/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.kt b/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.kt index cea60f0beb..e2a61d7451 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/OTGUtil.kt @@ -37,6 +37,7 @@ import com.amaze.filemanager.fileoperations.filesystem.usb.SingletonUsbOtg import com.amaze.filemanager.fileoperations.filesystem.usb.UsbOtgRepresentation import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.filesystem.RootHelper +import java.net.URLDecoder /** Created by Vishal on 27-04-2017. */ object OTGUtil { @@ -170,7 +171,7 @@ object OTGUtil { var retval: DocumentFile? = DocumentFile.fromTreeUri(context, rootUri) ?: throw DocumentFileNotFoundException(rootUri, path) val parts: Array = if (openMode == OpenMode.DOCUMENT_FILE) { - path.substringAfter(rootUri.toString()) + path.substringAfter(URLDecoder.decode(rootUri.toString(), Charsets.UTF_8.name())) .split("/", PATH_SEPARATOR_ENCODED).toTypedArray() } else { path.split("/").toTypedArray() @@ -180,7 +181,7 @@ object OTGUtil { if (part == "otg:" || part == "" || part == "content:") continue // iterating through the required path to find the end point - var nextDocument = retval?.findFile(part) ?: retval + var nextDocument = retval?.findFile(part) if (createRecursive && (nextDocument == null || !nextDocument.exists())) { nextDocument = retval?.createFile(part.substring(part.lastIndexOf(".")), part) } diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt index 83ec4ceb87..4d2daafd78 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt @@ -23,6 +23,7 @@ package com.amaze.filemanager.filesystem import android.os.Build.VERSION_CODES.JELLY_BEAN import android.os.Build.VERSION_CODES.KITKAT import android.os.Build.VERSION_CODES.P +import android.os.Environment import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.amaze.filemanager.application.AppConfig @@ -32,9 +33,11 @@ import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config +import java.io.File import java.net.URLDecoder import kotlin.random.Random +/* ktlint-disable max-line-length */ @RunWith(AndroidJUnit4::class) @Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P]) class HybridFileTest { @@ -77,11 +80,119 @@ class HybridFileTest { fun testSshGetParentWithoutTrailingSlash() { val file = HybridFile(OpenMode.SFTP, "ssh://user@127.0.0.1/home/user/next/project") assertEquals( - "ssh://user@127.0.0.1/home/user/next", + "ssh://user@127.0.0.1/home/user/next/", file.getParent(ApplicationProvider.getApplicationContext()) ) } + /** + * Test [HybridFile.getParent] for Android/data path. + */ + @Test + fun testDocumentFileAndroidDataGetParent1() { + var file = HybridFile( + OpenMode.DOCUMENT_FILE, + "content://com.android.externalstorage.documents/tree/primary:Android/data/com.amaze.filemanager.debug/cache" + ) + assertEquals( + "content://com.android.externalstorage.documents/tree/primary:Android/data/com.amaze.filemanager.debug/", + file.getParent(AppConfig.getInstance()) + ) + file = HybridFile( + OpenMode.DOCUMENT_FILE, + file.getParent(AppConfig.getInstance()) + ) + assertEquals( + File(Environment.getExternalStorageDirectory(), "Android/data").absolutePath, + file.getParent(AppConfig.getInstance()) + ) + } + + /** + * Test [HybridFile.getParent] for Android/data path (again). + */ + @Test + fun testDocumentFileAndroidDataGetParent2() { + var file = HybridFile( + OpenMode.DOCUMENT_FILE, + "content://com.android.externalstorage.documents/tree/primary:Android/data/com.amaze.filemanager.debug/files/external" + ) + assertEquals( + "content://com.android.externalstorage.documents/tree/primary:Android/data/com.amaze.filemanager.debug/files/", + file.getParent(AppConfig.getInstance()) + ) + file = HybridFile( + OpenMode.DOCUMENT_FILE, + file.getParent(AppConfig.getInstance()) + ) + assertEquals( + "content://com.android.externalstorage.documents/tree/primary:Android/data/com.amaze.filemanager.debug/", + file.getParent(AppConfig.getInstance()) + ) + file = HybridFile( + OpenMode.DOCUMENT_FILE, + file.getParent(AppConfig.getInstance()) + ) + assertEquals( + File(Environment.getExternalStorageDirectory(), "Android/data").absolutePath, + file.getParent(AppConfig.getInstance()) + ) + } + + /** + * Test [HybridFile.getParent] for Android/obb path. + */ + @Test + fun testDocumentFileAndroidObbGetParent3() { + var file = HybridFile( + OpenMode.DOCUMENT_FILE, + "content://com.android.externalstorage.documents/tree/primary:Android/obb/com.amaze.filemanager.debug/cache" + ) + assertEquals( + "content://com.android.externalstorage.documents/tree/primary:Android/obb/com.amaze.filemanager.debug/", + file.getParent(AppConfig.getInstance()) + ) + file = HybridFile( + OpenMode.DOCUMENT_FILE, + file.getParent(AppConfig.getInstance()) + ) + assertEquals( + File(Environment.getExternalStorageDirectory(), "Android/obb").absolutePath, + file.getParent(AppConfig.getInstance()) + ) + } + + /** + * Test [HybridFile.getParent] for Android/obb path (again). + */ + @Test + fun testDocumentFileAndroidObbGetParent2() { + var file = HybridFile( + OpenMode.DOCUMENT_FILE, + "content://com.android.externalstorage.documents/tree/primary:Android/obb/com.amaze.filemanager.debug/files/external" + ) + assertEquals( + "content://com.android.externalstorage.documents/tree/primary:Android/obb/com.amaze.filemanager.debug/files/", + file.getParent(AppConfig.getInstance()) + ) + file = HybridFile( + OpenMode.DOCUMENT_FILE, + file.getParent(AppConfig.getInstance()) + ) + assertEquals( + "content://com.android.externalstorage.documents/tree/primary:Android/obb/com.amaze.filemanager.debug/", + file.getParent(AppConfig.getInstance()) + ) + file = HybridFile( + OpenMode.DOCUMENT_FILE, + file.getParent(AppConfig.getInstance()) + ) + assertEquals( + File(Environment.getExternalStorageDirectory(), "Android/obb").absolutePath, + file.getParent(AppConfig.getInstance()) + ) + } + /** * Test [HybridFile.getName] */ @@ -115,3 +226,4 @@ class HybridFileTest { assertEquals("down the pipe", file.getName(AppConfig.getInstance())) } } +/* ktlint-enable max-line-length */ diff --git a/file_operations/src/main/java/com/amaze/filemanager/fileoperations/filesystem/OpenMode.java b/file_operations/src/main/java/com/amaze/filemanager/fileoperations/filesystem/OpenMode.java index 407bef48ae..227e24fb80 100644 --- a/file_operations/src/main/java/com/amaze/filemanager/fileoperations/filesystem/OpenMode.java +++ b/file_operations/src/main/java/com/amaze/filemanager/fileoperations/filesystem/OpenMode.java @@ -45,7 +45,9 @@ public enum OpenMode { GDRIVE, DROPBOX, BOX, - ONEDRIVE; + ONEDRIVE, + + ANDROID_DATA; /** * Get open mode based on the id assigned. Generally used to retrieve this type after config From 805b81e557010b25095a9ecbc4144ce61117df66 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sun, 6 Nov 2022 23:36:53 +0800 Subject: [PATCH 19/25] Decouple some selection paths to separate internal methods To work around Codacy complaints on long methods --- .../asynctasks/LoadFilesListTask.java | 356 ++++++++++-------- 1 file changed, 201 insertions(+), 155 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java index 5874c6780e..910216624c 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/LoadFilesListTask.java @@ -151,110 +151,32 @@ public LoadFilesListTask( switch (openmode) { case SMB: - if (hFile == null) { - hFile = new HybridFile(OpenMode.SMB, path); - } - if (!hFile.getPath().endsWith("/")) { - hFile.setPath(hFile.getPath() + "/"); - } - List smbCache = mainActivityViewModel.getFromListCache(path); - openmode = OpenMode.SMB; - if (smbCache != null && !forceReload) { - list = smbCache; - } else { - try { - SmbFile[] smbFile = hFile.getSmbFile(5000).listFiles(); - list = mainFragment.addToSmb(smbFile, path, showHiddenFiles); - } catch (SmbAuthException e) { - if (!e.getMessage().toLowerCase().contains("denied")) { - mainFragment.reauthenticateSmb(); - } - LOG.warn("failed to load smb list, authentication issue", e); - return null; - } catch (SmbException | NullPointerException e) { - LOG.warn("Failed to load smb files for path: " + path, e); - mainFragment.reauthenticateSmb(); - return null; - } - mainActivityViewModel.putInCache(path, list); - } + list = listSmb(hFile, mainActivityViewModel, mainFragment); break; case SFTP: - HybridFile ftpHFile = new HybridFile(openmode, path); - List sftpCache = mainActivityViewModel.getFromListCache(path); - if (sftpCache != null && !forceReload) { - list = sftpCache; - } else { - list = new ArrayList<>(); - ftpHFile.forEachChildrenFile( - context, - false, - file -> { - if (!(dataUtils.isFileHidden(file.getPath()) - || file.isHidden() && !showHiddenFiles)) { - LayoutElementParcelable elem = createListParcelables(file); - if (elem != null) { - list.add(elem); - } - } - }); - mainActivityViewModel.putInCache(path, list); - } + list = listSftp(mainActivityViewModel); break; case CUSTOM: list = getCachedMediaList(mainActivityViewModel); break; case OTG: - list = new ArrayList<>(); - listOtg( - path, - file -> { - LayoutElementParcelable elem = createListParcelables(file); - if (elem != null) list.add(elem); - }); + list = listOtg(); openmode = OpenMode.OTG; break; case DOCUMENT_FILE: - List cache = mainActivityViewModel.getFromListCache(path); - if (cache != null && !forceReload) { - list = cache; - } else { - list = new ArrayList<>(); - listDocumentFiles( - file -> { - LayoutElementParcelable elem = createListParcelables(file); - if (elem != null) list.add(elem); - }); - mainActivityViewModel.putInCache(path, list); - } + list = listDocumentFiles(mainActivityViewModel); openmode = OpenMode.DOCUMENT_FILE; break; case DROPBOX: case BOX: case GDRIVE: case ONEDRIVE: - List cloudCache = mainActivityViewModel.getFromListCache(path); - if (cloudCache != null && !forceReload) { - list = cloudCache; - } else { - CloudStorage cloudStorage = dataUtils.getAccount(openmode); - list = new ArrayList<>(); - try { - listCloud( - path, - cloudStorage, - openmode, - file -> { - LayoutElementParcelable elem = createListParcelables(file); - if (elem != null) list.add(elem); - }); - mainActivityViewModel.putInCache(path, list); - } catch (CloudPluginException e) { - LOG.warn("failed to load cloud files", e); - AppConfig.toast( - context, context.getResources().getString(R.string.failed_no_connection)); - return new Pair<>(openmode, list); - } + try { + list = listCloud(mainActivityViewModel); + } catch (CloudPluginException e) { + LOG.warn("failed to load cloud files", e); + AppConfig.toast(context, context.getResources().getString(R.string.failed_no_connection)); + return new Pair<>(openmode, Collections.emptyList()); } break; case ANDROID_DATA: @@ -262,66 +184,13 @@ public LoadFilesListTask( break; default: // we're neither in OTG not in SMB, load the list based on root/general filesystem - List localCache = mainActivityViewModel.getFromListCache(path); - openmode = - ListFilesCommand.INSTANCE.getOpenMode( - path, mainFragment.getMainActivity().isRootExplorer()); - if (localCache != null && !forceReload) { - list = localCache; - } else { - list = new ArrayList<>(); - final OpenMode[] currentOpenMode = new OpenMode[1]; - ListFilesCommand.INSTANCE.listFiles( - path, - mainFragment.getMainActivity().isRootExplorer(), - showHiddenFiles, - mode -> { - currentOpenMode[0] = mode; - return null; - }, - hybridFileParcelable -> { - LayoutElementParcelable elem = createListParcelables(hybridFileParcelable); - if (elem != null) list.add(elem); - return null; - }); - if (list.size() > MainActivityViewModel.Companion.getCACHE_LOCAL_LIST_THRESHOLD()) { - mainActivityViewModel.putInCache(path, list); - } - if (null != currentOpenMode[0]) { - openmode = currentOpenMode[0]; - } - } + list = listDefault(mainActivityViewModel, mainFragment); break; } if (list != null && !(openmode == OpenMode.CUSTOM && ((path).equals("5") || (path).equals("6")))) { - int t = SortHandler.getSortType(context, path); - int sortby; - int asc; - if (t <= 3) { - sortby = t; - asc = 1; - } else { - asc = -1; - sortby = t - 4; - } - - MainFragmentViewModel viewModel = mainFragment.getMainFragmentViewModel(); - - for (LayoutElementParcelable layoutElementParcelable : list) { - if (layoutElementParcelable.isDirectory) { - viewModel.setFolderCount(mainFragment.getMainFragmentViewModel().getFolderCount() + 1); - } else { - viewModel.setFileCount(mainFragment.getMainFragmentViewModel().getFileCount() + 1); - } - } - - if (viewModel != null) { - Collections.sort(list, new FileListSorter(viewModel.getDsort(), sortby, asc)); - } else { - LOG.error("MainFragmentViewModel is null, this is a bug"); - } + postListCustomPathProcess(list, mainFragment); } return new Pair<>(openmode, list); @@ -380,6 +249,36 @@ private List getCachedMediaList( return list; } + private void postListCustomPathProcess( + @NonNull List list, @NonNull MainFragment mainFragment) { + int t = SortHandler.getSortType(context.get(), path); + int sortby; + int asc; + if (t <= 3) { + sortby = t; + asc = 1; + } else { + asc = -1; + sortby = t - 4; + } + + MainFragmentViewModel viewModel = mainFragment.getMainFragmentViewModel(); + + for (LayoutElementParcelable layoutElementParcelable : list) { + if (layoutElementParcelable.isDirectory) { + viewModel.setFolderCount(mainFragment.getMainFragmentViewModel().getFolderCount() + 1); + } else { + viewModel.setFileCount(mainFragment.getMainFragmentViewModel().getFileCount() + 1); + } + } + + if (viewModel != null) { + Collections.sort(list, new FileListSorter(viewModel.getDsort(), sortby, asc)); + } else { + LOG.error("MainFragmentViewModel is null, this is a bug"); + } + } + private @Nullable LayoutElementParcelable createListParcelables(HybridFileParcelable baseFile) { if (dataUtils.isFileHidden(baseFile.getPath())) { return null; @@ -424,23 +323,23 @@ private List getCachedMediaList( return layoutElement; } - private ArrayList listImages() { + private List listImages() { final String[] projection = {MediaStore.Images.Media.DATA}; return listMediaCommon(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null); } - private ArrayList listVideos() { + private List listVideos() { final String[] projection = {MediaStore.Video.Media.DATA}; return listMediaCommon(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null); } - private ArrayList listaudio() { + private List listaudio() { String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0"; String[] projection = {MediaStore.Audio.Media.DATA}; return listMediaCommon(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection); } - private @Nullable ArrayList listMediaCommon( + private @Nullable List listMediaCommon( Uri contentUri, @NonNull String[] projection, @Nullable String selection) { final Context context = this.context.get(); @@ -468,7 +367,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return retval; } - private @Nullable ArrayList listDocs() { + private @Nullable List listDocs() { final Context context = this.context.get(); if (context == null) { @@ -524,7 +423,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return docs; } - private @Nullable ArrayList listApks() { + private @Nullable List listApks() { final Context context = this.context.get(); if (context == null) { @@ -557,7 +456,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return apks; } - private @Nullable ArrayList listRecent() { + private @Nullable List listRecent() { final MainFragment mainFragment = this.mainFragmentReference.get(); if (mainFragment == null) { cancel(true); @@ -585,7 +484,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return songs; } - private @Nullable ArrayList listRecentFiles() { + private @Nullable List listRecentFiles() { final Context context = this.context.get(); if (context == null) { @@ -593,7 +492,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return null; } - ArrayList recentFiles = new ArrayList<>(20); + List recentFiles = new ArrayList<>(20); final String[] projection = { MediaStore.Files.FileColumns.DATA, MediaStore.Files.FileColumns.DATE_MODIFIED }; @@ -679,6 +578,153 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { return retval; } + private List listSmb( + @Nullable final HybridFile hFile, + @NonNull MainActivityViewModel mainActivityViewModel, + @NonNull MainFragment mainFragment) { + HybridFile _file = hFile; + if (_file == null) { + _file = new HybridFile(OpenMode.SMB, path); + } + if (!_file.getPath().endsWith("/")) { + _file.setPath(hFile.getPath() + "/"); + } + @NonNull List list; + List smbCache = mainActivityViewModel.getFromListCache(path); + openmode = OpenMode.SMB; + if (smbCache != null && !forceReload) { + list = smbCache; + } else { + try { + SmbFile[] smbFile = _file.getSmbFile(5000).listFiles(); + list = mainFragment.addToSmb(smbFile, path, showHiddenFiles); + } catch (SmbAuthException e) { + if (!e.getMessage().toLowerCase().contains("denied")) { + mainFragment.reauthenticateSmb(); + } + LOG.warn("failed to load smb list, authentication issue", e); + return null; + } catch (SmbException | NullPointerException e) { + LOG.warn("Failed to load smb files for path: " + path, e); + mainFragment.reauthenticateSmb(); + return null; + } + mainActivityViewModel.putInCache(path, list); + } + return list; + } + + private List listSftp( + @NonNull MainActivityViewModel mainActivityViewModel) { + HybridFile ftpHFile = new HybridFile(openmode, path); + List list; + List sftpCache = mainActivityViewModel.getFromListCache(path); + if (sftpCache != null && !forceReload) { + list = sftpCache; + } else { + list = new ArrayList<>(); + ftpHFile.forEachChildrenFile( + context.get(), + false, + file -> { + if (!(dataUtils.isFileHidden(file.getPath()) || file.isHidden() && !showHiddenFiles)) { + LayoutElementParcelable elem = createListParcelables(file); + if (elem != null) { + list.add(elem); + } + } + }); + mainActivityViewModel.putInCache(path, list); + } + return list; + } + + private List listOtg() { + List list = new ArrayList<>(); + listOtgInternal( + path, + file -> { + LayoutElementParcelable elem = createListParcelables(file); + if (elem != null) list.add(elem); + }); + return list; + } + + private List listDocumentFiles( + @NonNull MainActivityViewModel mainActivityViewModel) { + List list; + List cache = mainActivityViewModel.getFromListCache(path); + if (cache != null && !forceReload) { + list = cache; + } else { + list = new ArrayList<>(); + listDocumentFilesInternal( + file -> { + LayoutElementParcelable elem = createListParcelables(file); + if (elem != null) list.add(elem); + }); + mainActivityViewModel.putInCache(path, list); + } + return list; + } + + private List listCloud( + @NonNull MainActivityViewModel mainActivityViewModel) throws CloudPluginException { + List list; + List cloudCache = mainActivityViewModel.getFromListCache(path); + if (cloudCache != null && !forceReload) { + list = cloudCache; + } else { + CloudStorage cloudStorage = dataUtils.getAccount(openmode); + list = new ArrayList<>(); + listCloudInternal( + path, + cloudStorage, + openmode, + file -> { + LayoutElementParcelable elem = createListParcelables(file); + if (elem != null) list.add(elem); + }); + mainActivityViewModel.putInCache(path, list); + } + return list; + } + + private List listDefault( + @NonNull MainActivityViewModel mainActivityViewModel, @NonNull MainFragment mainFragment) { + List list; + List localCache = mainActivityViewModel.getFromListCache(path); + openmode = + ListFilesCommand.INSTANCE.getOpenMode( + path, mainFragment.requireMainActivity().isRootExplorer()); + if (localCache != null && !forceReload) { + list = localCache; + } else { + list = new ArrayList<>(); + final OpenMode[] currentOpenMode = new OpenMode[1]; + ListFilesCommand.INSTANCE.listFiles( + path, + mainFragment.requireMainActivity().isRootExplorer(), + showHiddenFiles, + mode -> { + currentOpenMode[0] = mode; + return null; + }, + hybridFileParcelable -> { + LayoutElementParcelable elem = createListParcelables(hybridFileParcelable); + if (elem != null) list.add(elem); + return null; + }); + if (list.size() > MainActivityViewModel.Companion.getCACHE_LOCAL_LIST_THRESHOLD()) { + mainActivityViewModel.putInCache(path, list); + } + if (null != currentOpenMode[0]) { + openmode = currentOpenMode[0]; + } + } + return list; + } + /** * Lists files from an OTG device * @@ -686,7 +732,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { * com.amaze.filemanager.utils.OTGUtil#PREFIX_OTG} Independent of URI (or mount point) for the * OTG */ - private void listOtg(String path, OnFileFound fileFound) { + private void listOtgInternal(String path, OnFileFound fileFound) { final Context context = this.context.get(); if (context == null) { @@ -697,7 +743,7 @@ private void listOtg(String path, OnFileFound fileFound) { OTGUtil.getDocumentFiles(path, context, fileFound); } - private void listDocumentFiles(OnFileFound fileFound) { + private void listDocumentFilesInternal(OnFileFound fileFound) { final Context context = this.context.get(); if (context == null) { @@ -709,7 +755,7 @@ private void listDocumentFiles(OnFileFound fileFound) { SafRootHolder.getUriRoot(), path, context, OpenMode.DOCUMENT_FILE, fileFound); } - private void listCloud( + private void listCloudInternal( String path, CloudStorage cloudStorage, OpenMode openMode, OnFileFound fileFoundCallback) throws CloudPluginException { final Context context = this.context.get(); From 8a45505163f517dcbdc964e65b3fc69a03d59a4d Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Thu, 17 Nov 2022 04:00:15 +0530 Subject: [PATCH 20/25] Fix codecy issue --- .../java/com/amaze/filemanager/ui/views/drawer/Drawer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 4d03a4b2d2..c55fb95000 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -305,7 +305,8 @@ public void refreshDrawer() { HybridFile hybridFile = new HybridFile(OpenMode.UNKNOWN, file); hybridFile.generateMode(mainActivity); - long totalSpace = hybridFile.getTotal(mainActivity), freeSpace = hybridFile.getUsableSpace(); + long totalSpace = hybridFile.getTotal(mainActivity); + long freeSpace = hybridFile.getUsableSpace(); storageDirectoryPaths.add(file); From 7c0961e8a022b444842920b3b941997d081eb4f1 Mon Sep 17 00:00:00 2001 From: TranceLove Date: Wed, 21 Sep 2022 15:33:45 +0800 Subject: [PATCH 21/25] Check current working directory and change to root Fixes #3476. Also fixes line parsing for filenames having colon, like cpu_variant:arm. --- .../amaze/filemanager/filesystem/root/ListFilesCommand.kt | 6 ++++++ .../filemanager/filesystem/root/ListFilesCommandTest.kt | 8 ++++++++ .../filemanager/filesystem/root/ListFilesCommandTest2.kt | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt b/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt index bf936334e3..3b2964fc75 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/root/ListFilesCommand.kt @@ -124,6 +124,12 @@ object ListFilesCommand : IRootCommand() { PreferencesConstants.PREFERENCE_ROOT_LEGACY_LISTING, false ) + // #3476: Check current working dir, change back to / before proceeding + runShellCommand("pwd").run { + if (out.first() != "/") { + runShellCommand("cd /") + } + } return if (!retryWithLs && !enforceLegacyFileListing) { log.info("Using stat for list parsing") Pair( diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt index 36ad4b111f..e922d54ba0 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt @@ -32,6 +32,7 @@ import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.shadows.ShadowMultiDex import com.amaze.filemanager.test.ShadowNativeOperations import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants +import com.topjohnwu.superuser.Shell import io.mockk.every import io.mockk.mockkObject import org.junit.Assert.assertEquals @@ -90,6 +91,13 @@ class ListFilesCommandTest { every { ListFilesCommand.executeRootCommand(anyString(), anyBoolean(), anyBoolean()) } answers { callOriginal() } + every { ListFilesCommand.runShellCommand("pwd") }.answers { + object : Shell.Result() { + override fun getOut(): MutableList = listOf("/").toMutableList() + override fun getErr(): MutableList = emptyList().toMutableList() + override fun getCode(): Int = 0 + } + } every { ListFilesCommand.runShellCommandToList("ls -l \"/bin\"") } answers { lsLines } every { ListFilesCommand.runShellCommandToList("ls -l \"/\"") } answers { lsRootLines } every { diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt index 5755d9d018..da9ad65a4e 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt @@ -34,6 +34,7 @@ import com.amaze.filemanager.shadows.ShadowMultiDex import com.amaze.filemanager.test.ShadowNativeOperations import com.amaze.filemanager.test.TestUtils import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants +import com.topjohnwu.superuser.Shell import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -90,6 +91,13 @@ class ListFilesCommandTest2 { anyBoolean() ) ).thenCallRealMethod() + `when`(mockCommand.runShellCommand("pwd")).thenReturn( + object : Shell.Result() { + override fun getOut(): MutableList = listOf("/").toMutableList() + override fun getErr(): MutableList = emptyList().toMutableList() + override fun getCode(): Int = 0 + } + ) `when`(mockCommand.runShellCommandToList("ls -l \"/bin\"")).thenReturn(lsLines) `when`( mockCommand.runShellCommandToList( From 75b5d9a542e2d9cb3a2a71dc0c63c32665f3d1fd Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Mon, 14 Nov 2022 23:54:35 +0800 Subject: [PATCH 22/25] explorer.db upgrade fix So sorry for the woes... --- .../filemanager/database/ExplorerDatabase.kt | 35 ++++++++---- .../EncryptedStringTypeConverter.kt | 6 +-- .../database/ExplorerDatabaseMigrationTest.kt | 54 +++++++++++++------ 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt index fe06b88e75..65c9ec90e1 100644 --- a/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt +++ b/app/src/main/java/com/amaze/filemanager/database/ExplorerDatabase.kt @@ -71,7 +71,7 @@ abstract class ExplorerDatabase : RoomDatabase() { companion object { private const val DATABASE_NAME = "explorer.db" - const val DATABASE_VERSION = 10 + const val DATABASE_VERSION = 11 const val TABLE_TAB = "tab" const val TABLE_CLOUD_PERSIST = "cloud" const val TABLE_ENCRYPTED = "encrypted" @@ -156,7 +156,7 @@ abstract class ExplorerDatabase : RoomDatabase() { ) } } - internal val MIGRATION_6_7: Migration = object : Migration(6, DATABASE_VERSION) { + internal val MIGRATION_6_7: Migration = object : Migration(6, 7) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( "CREATE TABLE " + @@ -190,9 +190,9 @@ abstract class ExplorerDatabase : RoomDatabase() { " FROM " + TABLE_TAB ) - database.execSQL("DROP TABLE " + TABLE_TAB) + database.execSQL("DROP TABLE $TABLE_TAB") database.execSQL( - "ALTER TABLE " + TEMP_TABLE_PREFIX + TABLE_TAB + " RENAME TO " + TABLE_TAB + "ALTER TABLE $TEMP_TABLE_PREFIX$TABLE_TAB RENAME TO $TABLE_TAB" ) database.execSQL( "CREATE TABLE " + @@ -205,11 +205,11 @@ abstract class ExplorerDatabase : RoomDatabase() { " INTEGER NOT NULL)" ) database.execSQL( - "INSERT INTO " + TEMP_TABLE_PREFIX + TABLE_SORT + " SELECT * FROM " + TABLE_SORT + "INSERT INTO $TEMP_TABLE_PREFIX$TABLE_SORT SELECT * FROM $TABLE_SORT" ) - database.execSQL("DROP TABLE " + TABLE_SORT) + database.execSQL("DROP TABLE $TABLE_SORT") database.execSQL( - "ALTER TABLE " + TEMP_TABLE_PREFIX + TABLE_SORT + " RENAME TO " + TABLE_SORT + "ALTER TABLE $TEMP_TABLE_PREFIX$TABLE_SORT RENAME TO $TABLE_SORT" ) database.execSQL( "CREATE TABLE " + @@ -230,7 +230,7 @@ abstract class ExplorerDatabase : RoomDatabase() { " SELECT * FROM " + TABLE_ENCRYPTED ) - database.execSQL("DROP TABLE " + TABLE_ENCRYPTED) + database.execSQL("DROP TABLE $TABLE_ENCRYPTED") database.execSQL( "ALTER TABLE " + TEMP_TABLE_PREFIX + @@ -257,7 +257,7 @@ abstract class ExplorerDatabase : RoomDatabase() { " SELECT * FROM " + TABLE_CLOUD_PERSIST ) - database.execSQL("DROP TABLE " + TABLE_CLOUD_PERSIST) + database.execSQL("DROP TABLE $TABLE_CLOUD_PERSIST") database.execSQL( "ALTER TABLE " + TEMP_TABLE_PREFIX + @@ -294,7 +294,7 @@ abstract class ExplorerDatabase : RoomDatabase() { } } - internal val MIGRATION_9_10: Migration = object : Migration(9, DATABASE_VERSION) { + internal val MIGRATION_9_10: Migration = object : Migration(9, 10) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( "UPDATE " + @@ -308,6 +308,20 @@ abstract class ExplorerDatabase : RoomDatabase() { } } + internal val MIGRATION_10_11: Migration = object : Migration(10, DATABASE_VERSION) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "UPDATE " + + TABLE_CLOUD_PERSIST + + " SET " + + COLUMN_CLOUD_SERVICE + + " = " + + COLUMN_CLOUD_SERVICE + + "-2" + ) + } + } + /** * Initialize the database. Optionally, may provide a custom way to create the database * with supplied [Context]. @@ -329,6 +343,7 @@ abstract class ExplorerDatabase : RoomDatabase() { .addMigrations(MIGRATION_7_8) .addMigrations(MIGRATION_8_9) .addMigrations(MIGRATION_9_10) + .addMigrations(MIGRATION_10_11) .allowMainThreadQueries() .build() } diff --git a/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt b/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt index f596ce3177..ccf8bd8675 100644 --- a/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt +++ b/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt @@ -20,7 +20,6 @@ package com.amaze.filemanager.database.typeconverters -import android.util.Base64 import android.util.Log import androidx.room.TypeConverter import com.amaze.filemanager.application.AppConfig @@ -50,7 +49,7 @@ object EncryptedStringTypeConverter { fun toPassword(encryptedStringEntryInDb: String): StringWrapper { return runCatching { StringWrapper( - decryptPassword(AppConfig.getInstance(), encryptedStringEntryInDb, Base64.DEFAULT) + decryptPassword(AppConfig.getInstance(), encryptedStringEntryInDb) ) }.onFailure { Log.e(TAG, "Error decrypting password", it) @@ -68,8 +67,7 @@ object EncryptedStringTypeConverter { return runCatching { encryptPassword( AppConfig.getInstance(), - unencryptedPasswordString.value, - Base64.DEFAULT + unencryptedPasswordString.value ) }.onFailure { Log.e(TAG, "Error encrypting password", it) diff --git a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt index cf639f56f6..dd7a97b338 100644 --- a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt +++ b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt @@ -78,7 +78,9 @@ class ExplorerDatabaseMigrationTest { ExplorerDatabase.MIGRATION_5_6, ExplorerDatabase.MIGRATION_6_7, ExplorerDatabase.MIGRATION_7_8, - ExplorerDatabase.MIGRATION_8_9 + ExplorerDatabase.MIGRATION_8_9, + ExplorerDatabase.MIGRATION_9_10, + ExplorerDatabase.MIGRATION_10_11 ) .build() explorerDatabase.openHelper.writableDatabase @@ -98,7 +100,14 @@ class ExplorerDatabaseMigrationTest { ExplorerDatabase::class.java, TEST_DB ) - .addMigrations(ExplorerDatabase.MIGRATION_5_6, ExplorerDatabase.MIGRATION_6_7) + .addMigrations( + ExplorerDatabase.MIGRATION_5_6, + ExplorerDatabase.MIGRATION_6_7, + ExplorerDatabase.MIGRATION_7_8, + ExplorerDatabase.MIGRATION_8_9, + ExplorerDatabase.MIGRATION_9_10, + ExplorerDatabase.MIGRATION_10_11 + ) .build() explorerDatabase.openHelper.writableDatabase explorerDatabase.close() @@ -117,7 +126,13 @@ class ExplorerDatabaseMigrationTest { ExplorerDatabase::class.java, TEST_DB ) - .addMigrations(ExplorerDatabase.MIGRATION_6_7) + .addMigrations( + ExplorerDatabase.MIGRATION_6_7, + ExplorerDatabase.MIGRATION_7_8, + ExplorerDatabase.MIGRATION_8_9, + ExplorerDatabase.MIGRATION_9_10, + ExplorerDatabase.MIGRATION_10_11 + ) .build() explorerDatabase.openHelper.writableDatabase explorerDatabase.close() @@ -140,7 +155,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (1," + - (OpenMode.GDRIVE.ordinal - 3) + + (OpenMode.GDRIVE.ordinal - 1) + ",'abcd')" ) db.execSQL( @@ -153,7 +168,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (2," + - (OpenMode.DROPBOX.ordinal - 3) + + (OpenMode.DROPBOX.ordinal - 1) + ",'efgh')" ) db.execSQL( @@ -166,7 +181,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (3," + - (OpenMode.BOX.ordinal - 3) + + (OpenMode.BOX.ordinal - 1) + ",'ijkl')" ) db.execSQL( @@ -179,7 +194,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (4," + - (OpenMode.ONEDRIVE.ordinal - 3) + + (OpenMode.ONEDRIVE.ordinal - 1) + ",'mnop')" ) db.close() @@ -188,10 +203,12 @@ class ExplorerDatabaseMigrationTest { ExplorerDatabase::class.java, TEST_DB ) - .addMigrations(ExplorerDatabase.MIGRATION_7_8) - .addMigrations(ExplorerDatabase.MIGRATION_8_9) - .addMigrations(ExplorerDatabase.MIGRATION_9_10) - .allowMainThreadQueries() + .addMigrations( + ExplorerDatabase.MIGRATION_7_8, + ExplorerDatabase.MIGRATION_8_9, + ExplorerDatabase.MIGRATION_9_10, + ExplorerDatabase.MIGRATION_10_11 + ).allowMainThreadQueries() .build() explorerDatabase.openHelper.writableDatabase var verify = explorerDatabase @@ -242,7 +259,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (1," + - (OpenMode.GDRIVE.ordinal - 2) + + (OpenMode.GDRIVE.ordinal) + ",'abcd')" ) db.execSQL( @@ -255,7 +272,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (2," + - (OpenMode.DROPBOX.ordinal - 2) + + (OpenMode.DROPBOX.ordinal) + ",'efgh')" ) db.execSQL( @@ -268,7 +285,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (3," + - (OpenMode.BOX.ordinal - 2) + + (OpenMode.BOX.ordinal) + ",'ijkl')" ) db.execSQL( @@ -281,7 +298,7 @@ class ExplorerDatabaseMigrationTest { "," + ExplorerDatabase.COLUMN_CLOUD_PERSIST + ") VALUES (4," + - (OpenMode.ONEDRIVE.ordinal - 2) + + (OpenMode.ONEDRIVE.ordinal) + ",'mnop')" ) db.close() @@ -290,8 +307,11 @@ class ExplorerDatabaseMigrationTest { ExplorerDatabase::class.java, TEST_DB ) - .addMigrations(ExplorerDatabase.MIGRATION_8_9) - .addMigrations(ExplorerDatabase.MIGRATION_9_10) + .addMigrations( + ExplorerDatabase.MIGRATION_8_9, + ExplorerDatabase.MIGRATION_9_10, + ExplorerDatabase.MIGRATION_10_11 + ) .allowMainThreadQueries() .build() explorerDatabase.openHelper.writableDatabase From fd2d04e0ca8778e5fdee7e46229397993bc3e772 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Thu, 17 Nov 2022 20:11:13 +0530 Subject: [PATCH 23/25] fix merge conflicts --- app/src/main/res/values/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 014a993084..eb6d37215f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -794,6 +794,5 @@ You only need to do this once, until the next time you select a new location for Disable player intent filters Disables Amaze inbuilt media players from file chooser No app found to handle this intent. Do you have DocumentsUI installed? - "%s\n%s free of %s From e3a58cd150a68c6358f1d911b0732cab56931f92 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Thu, 17 Nov 2022 20:11:28 +0530 Subject: [PATCH 24/25] spotless --- .../com/amaze/filemanager/ui/fragments/MainFragment.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java index f02a3a1ffd..a04ae74723 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/MainFragment.java @@ -65,7 +65,6 @@ import com.amaze.filemanager.filesystem.files.FileListSorter; import com.amaze.filemanager.filesystem.files.FileUtils; import com.amaze.filemanager.ui.ExtensionsKt; -import com.amaze.filemanager.ui.ExtensionsKt; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.activities.MainActivityViewModel; import com.amaze.filemanager.ui.dialogs.GeneralDialogCreation; @@ -660,9 +659,9 @@ private OpenMode loadPathInQ(String actualPath, String providedPath, OpenMode pr .setOnClickListener( v -> { ExtensionsKt.runIfDocumentsUIExists( - intent, - requireMainActivity(), - () -> handleDocumentUriForRestrictedDirectories.launch(intent)); + intent, + requireMainActivity(), + () -> handleDocumentUriForRestrictedDirectories.launch(intent)); d.dismiss(); }); From 0b545cb7b6459fd5e903e94176be9b42972930ba Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Fri, 18 Nov 2022 18:03:04 +0530 Subject: [PATCH 25/25] Fix codacy issue --- .../java/com/amaze/filemanager/ui/Extensions.kt | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt index ca484d41cf..8b2b0f8013 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt @@ -31,9 +31,6 @@ import android.widget.EditText import android.widget.Toast import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig -import com.amaze.filemanager.ui.activities.MainActivity -import com.google.android.material.snackbar.BaseTransientBottomBar -import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputLayout import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -116,14 +113,13 @@ fun View.showFade(duration: Long) { this.visibility = View.VISIBLE } -fun Intent.runIfDocumentsUIExists(mainActivity: MainActivity, callback: Runnable) { - if (this.resolveActivity(mainActivity.packageManager) != null) { +/** + * Extension function to check for activity in package manager before triggering code + */ +fun Intent.runIfDocumentsUIExists(context: Context, callback: Runnable) { + if (this.resolveActivity(context.packageManager) != null) { callback.run() } else { - Snackbar.make( - mainActivity.findViewById(R.id.drawer_layout), - R.string.no_app_found_intent, - BaseTransientBottomBar.LENGTH_SHORT - ).show() + AppConfig.toast(context, R.string.no_app_found_intent) } }