From d039c8b25480ba204f0dd7f215806034db3fe3ed Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Sat, 11 Feb 2023 21:04:07 +0530 Subject: [PATCH 01/58] store recent search items --- .../PreferencesConstants.kt | 3 + .../ui/views/appbar/SearchView.java | 114 +++++++++++++++--- app/src/main/res/layout-v21/layout_search.xml | 96 +++++++++++---- .../main/res/layout-w720dp/layout_search.xml | 90 ++++++++++---- app/src/main/res/layout/layout_search.xml | 92 ++++++++++---- app/src/main/res/values/styles.xml | 8 ++ 6 files changed, 319 insertions(+), 84 deletions(-) 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 346066b6ad..91978ffd33 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 @@ -92,6 +92,9 @@ object PreferencesConstants { const val PREFERENCE_EXPORT_SETTINGS = "export_settings" const val PREFERENCE_IMPORT_SETTINGS = "import_settings" + // recent search items + const val PREFERENCE_RECENT_SEARCH_ITEMS = "recent_searches" + // others const val PREFERENCE_CURRENT_TAB = "" const val PREFERENCE_BOOKMARKS_ADDED = "books_added" diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index f6b1345d13..6a45f34055 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -23,25 +23,35 @@ import static android.content.Context.INPUT_METHOD_SERVICE; import static android.os.Build.VERSION.SDK_INT; +import java.util.ArrayList; + import com.amaze.filemanager.R; import com.amaze.filemanager.ui.activities.MainActivity; +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.theme.AppTheme; import com.amaze.filemanager.utils.Utils; +import com.google.android.material.chip.Chip; +import com.google.android.material.chip.ChipGroup; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.PorterDuff; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewAnimationUtils; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ImageView; -import android.widget.RelativeLayout; +import android.widget.TextView; import androidx.appcompat.widget.AppCompatEditText; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; +import androidx.preference.PreferenceManager; /** * SearchView, a simple view to search @@ -50,25 +60,33 @@ */ public class SearchView { - private MainActivity mainActivity; - private AppBar appbar; + private final MainActivity mainActivity; + private final AppBar appbar; + + private final ConstraintLayout searchViewLayout; + private final AppCompatEditText searchViewEditText; + private final ImageView clearImageView, backImageView; + private final TextView recentHintTV; + private final ChipGroup recentChipGroup; - private RelativeLayout searchViewLayout; - private AppCompatEditText searchViewEditText; - private ImageView clearImageView; - private ImageView backImageView; + private final SearchListener searchListener; private boolean enabled = false; - public SearchView( - final AppBar appbar, final MainActivity a, final SearchListener searchListener) { - mainActivity = a; + public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener searchListener) { + + this.mainActivity = mainActivity; + this.searchListener = searchListener; this.appbar = appbar; - searchViewLayout = a.findViewById(R.id.search_view); - searchViewEditText = a.findViewById(R.id.search_edit_text); - clearImageView = a.findViewById(R.id.search_close_btn); - backImageView = a.findViewById(R.id.img_view_back); + searchViewLayout = mainActivity.findViewById(R.id.search_view); + searchViewEditText = mainActivity.findViewById(R.id.search_edit_text); + clearImageView = mainActivity.findViewById(R.id.search_close_btn); + backImageView = mainActivity.findViewById(R.id.img_view_back); + recentChipGroup = mainActivity.findViewById(R.id.searchRecentItemsChipGroup); + recentHintTV = mainActivity.findViewById(R.id.searchRecentHintTV); + + initRecentSearches(mainActivity); clearImageView.setOnClickListener(v -> searchViewEditText.setText("")); @@ -77,16 +95,76 @@ public SearchView( searchViewEditText.setOnEditorActionListener( (v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_SEARCH) { - searchListener.onSearch(searchViewEditText.getText().toString()); + + String s = searchViewEditText.getText().toString(); + + searchListener.onSearch(s); appbar.getSearchView().hideSearchView(); + + String preferenceString = + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .getString(PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, null); + + ArrayList recentSearches = + preferenceString != null + ? new Gson() + .fromJson(preferenceString, new TypeToken>() {}.getType()) + : new ArrayList<>(); + + recentSearches.add(s); + + if (recentSearches.size() > 5) recentSearches.remove(0); + + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .edit() + .putString( + PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, + new Gson().toJson(recentSearches)) + .apply(); + + initRecentSearches(mainActivity); + return true; } return false; }); - initSearchViewColor(a); - // searchViewEditText.setTextColor(Utils.getColor(this, android.R.color.black)); - // searchViewEditText.setHintTextColor(Color.parseColor(ThemedActivity.accentSkin)); + initSearchViewColor(mainActivity); + } + + private void initRecentSearches(Context context) { + + String preferenceString = + PreferenceManager.getDefaultSharedPreferences(context) + .getString(PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, null); + + if (preferenceString == null) { + recentHintTV.setVisibility(View.GONE); + recentChipGroup.setVisibility(View.GONE); + return; + } + + recentHintTV.setVisibility(View.VISIBLE); + recentChipGroup.setVisibility(View.VISIBLE); + + recentChipGroup.removeAllViews(); + + ArrayList recentSearches = + new Gson().fromJson(preferenceString, new TypeToken>() {}.getType()); + + for (String string : recentSearches) { + Chip chip = new Chip(new ContextThemeWrapper(context, R.style.ChipStyle)); + + chip.setText(string); + + recentChipGroup.addView(chip); + + chip.setOnClickListener( + v -> { + searchListener.onSearch(((Chip) v).getText().toString()); + appbar.getSearchView().hideSearchView(); + }); + } } /** show search view with a circular reveal animation */ diff --git a/app/src/main/res/layout-v21/layout_search.xml b/app/src/main/res/layout-v21/layout_search.xml index b37cbaa518..3757da7b63 100644 --- a/app/src/main/res/layout-v21/layout_search.xml +++ b/app/src/main/res/layout-v21/layout_search.xml @@ -1,47 +1,97 @@ - + android:visibility="gone"> + android:layout_marginRight="@dimen/search_view_back_margin_left_right" + android:background="@drawable/ripple" + android:src="@drawable/ic_arrow_back_black_24dp" + app:layout_constraintBottom_toBottomOf="@id/search_edit_text" + app:layout_constraintEnd_toStartOf="@id/search_edit_text" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/search_edit_text" /> + + app:layout_constraintBottom_toTopOf="@id/searchRecentHintTV" + app:layout_constraintEnd_toStartOf="@id/search_close_btn" + app:layout_constraintStart_toEndOf="@id/img_view_back" + app:layout_constraintTop_toTopOf="parent" /> + - \ No newline at end of file + android:layout_marginRight="@dimen/search_view_info_margin_left_right" + android:background="@drawable/ripple" + android:src="@drawable/ic_close_black_24dp" + app:layout_constraintBottom_toBottomOf="@id/search_edit_text" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/search_edit_text" + app:layout_constraintTop_toTopOf="@id/search_edit_text" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp/layout_search.xml b/app/src/main/res/layout-w720dp/layout_search.xml index 09d90c2631..0da790bdf4 100644 --- a/app/src/main/res/layout-w720dp/layout_search.xml +++ b/app/src/main/res/layout-w720dp/layout_search.xml @@ -1,46 +1,94 @@ - + android:visibility="gone"> + android:layout_marginRight="@dimen/search_view_back_margin_left_right" + android:src="@drawable/ic_arrow_back_black_24dp" + app:layout_constraintBottom_toBottomOf="@id/search_edit_text" + app:layout_constraintEnd_toStartOf="@id/search_edit_text" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/search_edit_text" /> + + app:layout_constraintBottom_toTopOf="@id/searchRecentHintTV" + app:layout_constraintEnd_toStartOf="@id/search_close_btn" + app:layout_constraintStart_toEndOf="@id/img_view_back" + app:layout_constraintTop_toTopOf="parent" /> + - \ No newline at end of file + android:src="@drawable/ic_close_black_24dp" + app:layout_constraintBottom_toBottomOf="@id/search_edit_text" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/search_edit_text" + app:layout_constraintTop_toTopOf="@id/search_edit_text" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search.xml b/app/src/main/res/layout/layout_search.xml index 5c32fc38bb..0da790bdf4 100644 --- a/app/src/main/res/layout/layout_search.xml +++ b/app/src/main/res/layout/layout_search.xml @@ -1,46 +1,94 @@ - - + android:visibility="gone"> + android:layout_marginRight="@dimen/search_view_back_margin_left_right" + android:src="@drawable/ic_arrow_back_black_24dp" + app:layout_constraintBottom_toBottomOf="@id/search_edit_text" + app:layout_constraintEnd_toStartOf="@id/search_edit_text" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/search_edit_text" /> + + app:layout_constraintBottom_toTopOf="@id/searchRecentHintTV" + app:layout_constraintEnd_toStartOf="@id/search_close_btn" + app:layout_constraintStart_toEndOf="@id/img_view_back" + app:layout_constraintTop_toTopOf="parent" /> + - + android:layout_marginRight="@dimen/search_view_info_margin_left_right" + android:src="@drawable/ic_close_black_24dp" + app:layout_constraintBottom_toBottomOf="@id/search_edit_text" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/search_edit_text" + app:layout_constraintTop_toTopOf="@id/search_edit_text" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 88b8898f4d..747c2be575 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -317,4 +317,12 @@ + + + From 71db59f6f9b7109112830705d4da0bdcd0e225ce Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 21 Feb 2023 23:07:30 +0530 Subject: [PATCH 02/58] skip repeated queries and empty strings --- .../java/com/amaze/filemanager/ui/views/appbar/SearchView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 6a45f34055..c9d858c863 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -111,6 +111,8 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener .fromJson(preferenceString, new TypeToken>() {}.getType()) : new ArrayList<>(); + if (s.isEmpty() || recentSearches.contains(s)) return false; + recentSearches.add(s); if (recentSearches.size() > 5) recentSearches.remove(0); From c02806ebba6dffc240ee3fbeb759dc8ed0244f3f Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 22 Feb 2023 22:01:21 +0530 Subject: [PATCH 03/58] chore: fix codady --- .../com/amaze/filemanager/ui/views/appbar/SearchView.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index c9d858c863..efb717407c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -65,7 +65,10 @@ public class SearchView { private final ConstraintLayout searchViewLayout; private final AppCompatEditText searchViewEditText; - private final ImageView clearImageView, backImageView; + + private final ImageView clearImageView; + private final ImageView backImageView; + private final TextView recentHintTV; private final ChipGroup recentChipGroup; From 0657f9dd5a52b6fbc09cd5d266b0103ef0b02b24 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Thu, 23 Feb 2023 12:38:41 +0530 Subject: [PATCH 04/58] chore: fix failing CI --- app/src/main/res/layout/layout_search.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/layout/layout_search.xml b/app/src/main/res/layout/layout_search.xml index 0da790bdf4..04bc137d5e 100644 --- a/app/src/main/res/layout/layout_search.xml +++ b/app/src/main/res/layout/layout_search.xml @@ -9,17 +9,17 @@ android:gravity="center_vertical" android:visibility="gone"> - + app:layout_constraintTop_toTopOf="@id/search_edit_text" + app:srcCompat="@drawable/ic_arrow_back_black_24dp" /> - + app:layout_constraintTop_toTopOf="@id/search_edit_text" + app:srcCompat="@drawable/ic_close_black_24dp" /> Date: Mon, 13 Mar 2023 20:26:59 +0530 Subject: [PATCH 05/58] [WIP] simple search --- .../adapters/SearchRecyclerViewAdapter.kt | 89 +++++++++ .../ui/views/appbar/SearchView.java | 128 +++++++++--- app/src/main/res/layout-v21/layout_search.xml | 185 +++++++++++------- .../main/res/layout-w720dp/layout_search.xml | 168 +++++++++------- app/src/main/res/layout/layout_search.xml | 169 +++++++++------- app/src/main/res/layout/search_row_item.xml | 69 +++++++ 6 files changed, 554 insertions(+), 254 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt create mode 100644 app/src/main/res/layout/search_row_item.xml diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt new file mode 100644 index 0000000000..b9e7eefae5 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.adapters + +import android.text.format.Formatter +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.amaze.filemanager.R +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.utils.Utils +import java.util.* + +class SearchRecyclerViewAdapter : + ListAdapter( + + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: HybridFileParcelable, + newItem: HybridFileParcelable + ): Boolean { + return oldItem.path == newItem.path && oldItem.name == newItem.name + } + + override fun areContentsTheSame( + oldItem: HybridFileParcelable, + newItem: HybridFileParcelable + ): Boolean { + return oldItem.path == newItem.path && oldItem.name == newItem.name + } + } + ) { + override fun onCreateViewHolder(parent: ViewGroup, type: Int): ViewHolder { + val v: View = LayoutInflater.from(parent.context) + .inflate(R.layout.search_row_item, parent, false) + return ViewHolder(v) + } + + override fun onBindViewHolder(holder: SearchRecyclerViewAdapter.ViewHolder, position: Int) { + val item = getItem(position) + + holder.fileNameTV.text = item.name + + holder.dateTV.text = Utils.getDate(holder.itemView.context, item.date) + + if (!item.isDirectory) { + holder.sizeTV.text = Formatter.formatFileSize(holder.itemView.context, item.size) + } + } + + inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + + val fileNameTV: TextView + val dateTV: TextView + val sizeTV: TextView + + init { + + view.setOnClickListener { + } + + fileNameTV = view.findViewById(R.id.searchItemFileNameTV) + dateTV = view.findViewById(R.id.searchItemDateTV) + sizeTV = view.findViewById(R.id.searchItemSizeTV) + } + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index efb717407c..4285ac71c6 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -22,10 +22,14 @@ import static android.content.Context.INPUT_METHOD_SERVICE; import static android.os.Build.VERSION.SDK_INT; +import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES; import java.util.ArrayList; import com.amaze.filemanager.R; +import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; +import com.amaze.filemanager.filesystem.HybridFileParcelable; +import com.amaze.filemanager.filesystem.root.ListFilesCommand; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.theme.AppTheme; @@ -37,6 +41,7 @@ import android.animation.Animator; import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PorterDuff; import android.view.ContextThemeWrapper; @@ -49,9 +54,10 @@ import android.widget.TextView; import androidx.appcompat.widget.AppCompatEditText; -import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; +import androidx.core.widget.NestedScrollView; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.RecyclerView; /** * SearchView, a simple view to search @@ -63,16 +69,20 @@ public class SearchView { private final MainActivity mainActivity; private final AppBar appbar; - private final ConstraintLayout searchViewLayout; + private final NestedScrollView searchViewLayout; private final AppCompatEditText searchViewEditText; private final ImageView clearImageView; private final ImageView backImageView; private final TextView recentHintTV; + private final TextView searchResultsHintTV; + private final ChipGroup recentChipGroup; + private final RecyclerView recyclerView; private final SearchListener searchListener; + private final SearchRecyclerViewAdapter searchRecyclerViewAdapter; private boolean enabled = false; @@ -88,10 +98,21 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener backImageView = mainActivity.findViewById(R.id.img_view_back); recentChipGroup = mainActivity.findViewById(R.id.searchRecentItemsChipGroup); recentHintTV = mainActivity.findViewById(R.id.searchRecentHintTV); + searchResultsHintTV = mainActivity.findViewById(R.id.searchResultsHintTV); + recyclerView = mainActivity.findViewById(R.id.searchRecyclerView); initRecentSearches(mainActivity); - clearImageView.setOnClickListener(v -> searchViewEditText.setText("")); + searchRecyclerViewAdapter = new SearchRecyclerViewAdapter(); + recyclerView.setAdapter(searchRecyclerViewAdapter); + + clearRecyclerView(); + + clearImageView.setOnClickListener( + v -> { + searchViewEditText.setText(""); + clearRecyclerView(); + }); backImageView.setOnClickListener(v -> appbar.getSearchView().hideSearchView()); @@ -99,42 +120,77 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener (v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_SEARCH) { - String s = searchViewEditText.getText().toString(); + String s = searchViewEditText.getText().toString().trim(); - searchListener.onSearch(s); - appbar.getSearchView().hideSearchView(); + search(s); - String preferenceString = - PreferenceManager.getDefaultSharedPreferences(mainActivity) - .getString(PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, null); + saveRecentPreference(s); - ArrayList recentSearches = - preferenceString != null - ? new Gson() - .fromJson(preferenceString, new TypeToken>() {}.getType()) - : new ArrayList<>(); + return true; + } + return false; + }); - if (s.isEmpty() || recentSearches.contains(s)) return false; + initSearchViewColor(mainActivity); + } - recentSearches.add(s); + private void search(String s) { - if (recentSearches.size() > 5) recentSearches.remove(0); + clearRecyclerView(); - PreferenceManager.getDefaultSharedPreferences(mainActivity) - .edit() - .putString( - PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, - new Gson().toJson(recentSearches)) - .apply(); + searchResultsHintTV.setVisibility(View.VISIBLE); - initRecentSearches(mainActivity); + ArrayList hybridFileParcelables = new ArrayList<>(); - return true; - } - return false; + boolean showHiddenFiles = + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); + + ListFilesCommand.INSTANCE.listFiles( + mainActivity.getCurrentMainFragment().getPath(), + mainActivity.isRootExplorer(), + showHiddenFiles, + mode -> null, + hybridFileParcelable -> { + if (showHiddenFiles || !hybridFileParcelable.isHidden()) + if (hybridFileParcelable + .getName(mainActivity) + .toLowerCase() + .contains(s.toLowerCase())) { + hybridFileParcelables.add(hybridFileParcelable); + + searchRecyclerViewAdapter.submitList(hybridFileParcelables); + + searchRecyclerViewAdapter.notifyItemInserted(hybridFileParcelables.size() + 1); + } + return null; }); + } - initSearchViewColor(mainActivity); + private void saveRecentPreference(String s) { + + String preferenceString = + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .getString(PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, null); + + ArrayList recentSearches = + preferenceString != null + ? new Gson().fromJson(preferenceString, new TypeToken>() {}.getType()) + : new ArrayList<>(); + + if (s.isEmpty() || recentSearches.contains(s)) return; + + recentSearches.add(s); + + if (recentSearches.size() > 5) recentSearches.remove(0); + + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .edit() + .putString( + PreferencesConstants.PREFERENCE_RECENT_SEARCH_ITEMS, new Gson().toJson(recentSearches)) + .apply(); + + initRecentSearches(mainActivity); } private void initRecentSearches(Context context) { @@ -166,8 +222,10 @@ private void initRecentSearches(Context context) { chip.setOnClickListener( v -> { - searchListener.onSearch(((Chip) v).getText().toString()); - appbar.getSearchView().hideSearchView(); + String s = ((Chip) v).getText().toString(); + + searchViewEditText.setText(s); + search(s); }); } } @@ -251,6 +309,8 @@ public void hideSearchView() { animator = ObjectAnimator.ofFloat(searchViewLayout, "alpha", 1f, 0f); } + clearRecyclerView(); + // removing background fade view mainActivity.hideSmokeScreen(); animator.setInterpolator(new AccelerateDecelerateInterpolator()); @@ -316,6 +376,14 @@ private void initSearchViewColor(MainActivity a) { } } + @SuppressLint("NotifyDataSetChanged") + private void clearRecyclerView() { + searchRecyclerViewAdapter.submitList(new ArrayList<>()); + searchRecyclerViewAdapter.notifyDataSetChanged(); + + searchResultsHintTV.setVisibility(View.GONE); + } + public interface SearchListener { void onSearch(String queue); } diff --git a/app/src/main/res/layout-v21/layout_search.xml b/app/src/main/res/layout-v21/layout_search.xml index 3757da7b63..88222e41bf 100644 --- a/app/src/main/res/layout-v21/layout_search.xml +++ b/app/src/main/res/layout-v21/layout_search.xml @@ -1,97 +1,134 @@ - - + tools:ignore="ContentDescription"> - + - + - + - + - + + + + + + + android:layout_marginBottom="4dp" + android:text="Results" + android:textColor="@color/accent_material_light" + android:textSize="12sp" + android:visibility="gone" + app:layout_constraintBottom_toTopOf="@id/searchRecyclerView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/searchRecentItemsScrollView" /> - + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp/layout_search.xml b/app/src/main/res/layout-w720dp/layout_search.xml index 0da790bdf4..791fad9dc0 100644 --- a/app/src/main/res/layout-w720dp/layout_search.xml +++ b/app/src/main/res/layout-w720dp/layout_search.xml @@ -1,94 +1,112 @@ - - - - + tools:ignore="ContentDescription"> - + - + - + - + android:layout_marginBottom="4dp" + android:text="Recent" + android:textColor="@color/accent_material_light" + android:textSize="12sp" + app:layout_constraintBottom_toTopOf="@id/searchRecentItemsChipGroup" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/search_edit_text" /> + + - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search.xml b/app/src/main/res/layout/layout_search.xml index 04bc137d5e..a292e6af6b 100644 --- a/app/src/main/res/layout/layout_search.xml +++ b/app/src/main/res/layout/layout_search.xml @@ -1,94 +1,113 @@ - - - - + tools:ignore="ContentDescription"> - + - + - + - + android:layout_marginBottom="4dp" + android:text="Recent" + android:textColor="@color/accent_material_light" + android:textSize="12sp" + app:layout_constraintBottom_toTopOf="@id/searchRecentItemsChipGroup" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/search_edit_text" /> + + - + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_row_item.xml b/app/src/main/res/layout/search_row_item.xml new file mode 100644 index 0000000000..9bfca31161 --- /dev/null +++ b/app/src/main/res/layout/search_row_item.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + \ No newline at end of file From da13eb5a6253353a6b1cf3b00c7df6ba242498ad Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 3 May 2023 12:42:08 +0530 Subject: [PATCH 06/58] [WIP] simple search -- update UI --- .../adapters/SearchRecyclerViewAdapter.kt | 27 ++++++++------- .../ui/views/appbar/SearchView.java | 1 + app/src/main/res/layout/search_row_item.xml | 34 ++----------------- 3 files changed, 17 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index b9e7eefae5..42b4f6a400 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -20,7 +20,7 @@ package com.amaze.filemanager.adapters -import android.text.format.Formatter +import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -30,8 +30,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.filesystem.HybridFileParcelable -import com.amaze.filemanager.utils.Utils -import java.util.* +import java.util.Random class SearchRecyclerViewAdapter : ListAdapter( @@ -62,28 +61,30 @@ class SearchRecyclerViewAdapter : val item = getItem(position) holder.fileNameTV.text = item.name - - holder.dateTV.text = Utils.getDate(holder.itemView.context, item.date) - - if (!item.isDirectory) { - holder.sizeTV.text = Formatter.formatFileSize(holder.itemView.context, item.size) - } } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val fileNameTV: TextView - val dateTV: TextView - val sizeTV: TextView init { view.setOnClickListener { } + view.findViewById(R.id.searchItemSampleColorView) + .setBackgroundColor(getRandomColor()) + fileNameTV = view.findViewById(R.id.searchItemFileNameTV) - dateTV = view.findViewById(R.id.searchItemDateTV) - sizeTV = view.findViewById(R.id.searchItemSizeTV) + } + + private fun getRandomColor(): Int { + val colorArray = arrayOf( + "#e57373", "#f06292", "#ba68c8", "#9575cd", "#7986cb", "#64b5f6", "#4fc3f7", + "#4dd0e1", "#4db6ac", "#81c784", "#aed581", "#dce775", "#fff176", "#ffd54f", + "#ffb74d", "#ff8a65", "#a1887f" + ) + return Color.parseColor(colorArray[Random().nextInt(colorArray.size - 1)]) } } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 4285ac71c6..4d10bd43c7 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -146,6 +146,7 @@ private void search(String s) { PreferenceManager.getDefaultSharedPreferences(mainActivity) .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); + // TODO: takes too much resources & freezes main thread on huge folders ListFilesCommand.INSTANCE.listFiles( mainActivity.getCurrentMainFragment().getPath(), mainActivity.isRootExplorer(), diff --git a/app/src/main/res/layout/search_row_item.xml b/app/src/main/res/layout/search_row_item.xml index 9bfca31161..2069032c96 100644 --- a/app/src/main/res/layout/search_row_item.xml +++ b/app/src/main/res/layout/search_row_item.xml @@ -27,43 +27,13 @@ android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" - android:layout_marginBottom="2dp" + android:layout_marginBottom="8dp" android:textSize="16sp" - app:layout_constraintBottom_toTopOf="@id/searchItemSizeTV" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/searchItemSampleColorView" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0" /> - - - - \ No newline at end of file From b8f967c53f7b757ea7aeffc8b76901113a98b0e2 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 3 May 2023 22:55:26 +0530 Subject: [PATCH 07/58] simple search MVP --- .../adapters/SearchRecyclerViewAdapter.kt | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index 42b4f6a400..c3f233ceb5 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -20,16 +20,20 @@ package com.amaze.filemanager.adapters -import android.graphics.Color +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R +import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.colors.ColorPreference import java.util.Random class SearchRecyclerViewAdapter : @@ -69,22 +73,39 @@ class SearchRecyclerViewAdapter : init { - view.setOnClickListener { - } + fileNameTV = view.findViewById(R.id.searchItemFileNameTV) view.findViewById(R.id.searchItemSampleColorView) - .setBackgroundColor(getRandomColor()) + .setBackgroundColor(getRandomColor(view.context)) - fileNameTV = view.findViewById(R.id.searchItemFileNameTV) + view.setOnClickListener { + + val item = getItem(adapterPosition) + + if (!item.isDirectory) { + item.openFile( + AppConfig.getInstance().mainActivityContext as MainActivity?, + false + ) + } else { + (AppConfig.getInstance().mainActivityContext as MainActivity?) + ?.goToMain(item.path) + } + + (AppConfig.getInstance().mainActivityContext as MainActivity?) + ?.appbar?.searchView?.hideSearchView() + } } - private fun getRandomColor(): Int { - val colorArray = arrayOf( - "#e57373", "#f06292", "#ba68c8", "#9575cd", "#7986cb", "#64b5f6", "#4fc3f7", - "#4dd0e1", "#4db6ac", "#81c784", "#aed581", "#dce775", "#fff176", "#ffd54f", - "#ffb74d", "#ff8a65", "#a1887f" + private fun getRandomColor(context: Context): Int { + return ContextCompat.getColor( + context, + ColorPreference.availableColors[ + Random().nextInt( + ColorPreference.availableColors.size - 1 + ) + ] ) - return Color.parseColor(colorArray[Random().nextInt(colorArray.size - 1)]) } } } From be5d436d93f4c6096eb0b5c811dc2479e4cec76e Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 14:08:19 +0530 Subject: [PATCH 08/58] indexed search MVP --- .../ui/views/appbar/SearchView.java | 127 +++++++++++++++++- app/src/main/res/layout-v21/layout_search.xml | 18 ++- .../main/res/layout-w720dp/layout_search.xml | 18 ++- app/src/main/res/layout/layout_search.xml | 18 ++- app/src/main/res/values/strings.xml | 3 + 5 files changed, 175 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 4d10bd43c7..429b33e728 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -24,11 +24,14 @@ import static android.os.Build.VERSION.SDK_INT; import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES; +import java.io.File; import java.util.ArrayList; +import java.util.List; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; import com.amaze.filemanager.filesystem.HybridFileParcelable; +import com.amaze.filemanager.filesystem.RootHelper; import com.amaze.filemanager.filesystem.root.ListFilesCommand; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; @@ -43,7 +46,11 @@ import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Context; +import android.database.Cursor; import android.graphics.PorterDuff; +import android.provider.MediaStore; +import android.text.Editable; +import android.text.TextWatcher; import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewAnimationUtils; @@ -77,6 +84,7 @@ public class SearchView { private final TextView recentHintTV; private final TextView searchResultsHintTV; + private final TextView deepSearchTV; private final ChipGroup recentChipGroup; private final RecyclerView recyclerView; @@ -84,8 +92,15 @@ public class SearchView { private final SearchListener searchListener; private final SearchRecyclerViewAdapter searchRecyclerViewAdapter; + // 0 -> Basic + // 1 -> Indexed + // 2 -> Recursive + private int searchMode; + private boolean enabled = false; + @SuppressWarnings("ConstantConditions") + @SuppressLint("NotifyDataSetChanged") public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener searchListener) { this.mainActivity = mainActivity; @@ -99,8 +114,16 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener recentChipGroup = mainActivity.findViewById(R.id.searchRecentItemsChipGroup); recentHintTV = mainActivity.findViewById(R.id.searchRecentHintTV); searchResultsHintTV = mainActivity.findViewById(R.id.searchResultsHintTV); + deepSearchTV = mainActivity.findViewById(R.id.searchDeepSearchTV); recyclerView = mainActivity.findViewById(R.id.searchRecyclerView); + searchMode = 0; + deepSearchTV.setText( + String.format( + "%s%s", + mainActivity.getString(R.string.not_finding_what_you_re_looking_for), + mainActivity.getString(R.string.try_indexed_search))); + initRecentSearches(mainActivity); searchRecyclerViewAdapter = new SearchRecyclerViewAdapter(); @@ -116,29 +139,86 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener backImageView.setOnClickListener(v -> appbar.getSearchView().hideSearchView()); + searchViewEditText.addTextChangedListener( + new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + if (count > 0) searchViewEditText.setError(null); + + if (count >= 3) onSearch(false); + } + + @Override + public void afterTextChanged(Editable s) {} + }); + searchViewEditText.setOnEditorActionListener( (v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_SEARCH) { + if (actionId == EditorInfo.IME_ACTION_SEARCH) return onSearch(true); + + return false; + }); - String s = searchViewEditText.getText().toString().trim(); + deepSearchTV.setOnClickListener( + v -> { + if (searchMode == 1) { - search(s); + List hybridFileParcelables = + indexedSearch(mainActivity, searchViewEditText.getText().toString().trim()); + + searchRecyclerViewAdapter.submitList(hybridFileParcelables); + searchRecyclerViewAdapter.notifyDataSetChanged(); + + searchMode = 2; + deepSearchTV.setText( + String.format( + "%s%s", + mainActivity.getString(R.string.not_finding_what_you_re_looking_for), + mainActivity.getString(R.string.try_recursive_search))); - saveRecentPreference(s); + } else if (searchMode == 2) { - return true; + deepSearchTV.setVisibility(View.GONE); } - return false; }); initSearchViewColor(mainActivity); } + @SuppressWarnings("ConstantConditions") + private boolean onSearch(boolean shouldSave) { + + String s = searchViewEditText.getText().toString().trim(); + + if (s.isEmpty()) { + searchViewEditText.setError(mainActivity.getString(R.string.field_empty)); + searchViewEditText.requestFocus(); + return false; + } + + search(s); + + if (shouldSave) saveRecentPreference(s); + + return true; + } + private void search(String s) { clearRecyclerView(); searchResultsHintTV.setVisibility(View.VISIBLE); + deepSearchTV.setVisibility(View.VISIBLE); + searchMode = 1; + deepSearchTV.setText( + String.format( + "%s%s", + mainActivity.getString(R.string.not_finding_what_you_re_looking_for), + mainActivity.getString(R.string.try_indexed_search))); ArrayList hybridFileParcelables = new ArrayList<>(); @@ -231,6 +311,41 @@ private void initRecentSearches(Context context) { } } + private List indexedSearch(MainActivity mainActivity, String query) { + + ArrayList list = new ArrayList<>(); + final String[] projection = {MediaStore.Files.FileColumns.DATA}; + + Cursor cursor = + mainActivity + .getContentResolver() + .query(MediaStore.Files.getContentUri("external"), projection, null, null, null); + + if (cursor == null) return list; + else if (cursor.getCount() > 0 && cursor.moveToFirst()) { + do { + String path = + cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); + + if (path != null + && path.contains(mainActivity.getCurrentMainFragment().getPath()) + && path.toLowerCase().contains(query.toLowerCase())) { + + boolean showHiddenFiles = + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); + + HybridFileParcelable hybridFileParcelable = + RootHelper.generateBaseFile(new File(path), showHiddenFiles); + + if (hybridFileParcelable != null) list.add(hybridFileParcelable); + } + } while (cursor.moveToNext()); + } + cursor.close(); + return list; + } + /** show search view with a circular reveal animation */ public void revealSearchView() { final int START_RADIUS = 16; diff --git a/app/src/main/res/layout-v21/layout_search.xml b/app/src/main/res/layout-v21/layout_search.xml index 88222e41bf..eda8994a46 100644 --- a/app/src/main/res/layout-v21/layout_search.xml +++ b/app/src/main/res/layout-v21/layout_search.xml @@ -125,10 +125,26 @@ android:layout_height="wrap_content" android:nestedScrollingEnabled="false" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/searchDeepSearchTV" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchResultsHintTV" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp/layout_search.xml b/app/src/main/res/layout-w720dp/layout_search.xml index 791fad9dc0..03e740801b 100644 --- a/app/src/main/res/layout-w720dp/layout_search.xml +++ b/app/src/main/res/layout-w720dp/layout_search.xml @@ -103,10 +103,26 @@ android:layout_width="@dimen/zero_dp" android:layout_height="wrap_content" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/searchDeepSearchTV" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchRecentItemsScrollView" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search.xml b/app/src/main/res/layout/layout_search.xml index a292e6af6b..c81bcdc7f3 100644 --- a/app/src/main/res/layout/layout_search.xml +++ b/app/src/main/res/layout/layout_search.xml @@ -104,10 +104,26 @@ android:layout_height="wrap_content" android:nestedScrollingEnabled="false" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/searchDeepSearchTV" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchRecentItemsScrollView" /> + + \ 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 9ef5b0be6f..24254559dc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -809,5 +809,8 @@ You only need to do this once, until the next time you select a new location for No app found to handle this intent. Do you have DocumentsUI installed? Cloud Connection credentials cleared Unfortunately, we were unable to migrate your cloud connection credentials to the new database schema, and we had to remove them from the app. Please create the cloud connection again. + Not finding what you\'re looking for? + Try Recursive Search! + Try Indexed Search! From eb2a7ac456138546fd1f39b11fa85cef26f52322 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 15:06:12 +0530 Subject: [PATCH 09/58] improve UX --- .../ui/views/appbar/SearchView.java | 16 +++++++--- .../com/amaze/filemanager/utils/Utils.java | 8 +++++ app/src/main/res/layout-v21/layout_search.xml | 10 +++--- .../main/res/layout-w720dp/layout_search.xml | 30 ++++++++++++++---- app/src/main/res/layout/layout_search.xml | 31 ++++++++++++++----- app/src/main/res/values/strings.xml | 2 ++ 6 files changed, 75 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 429b33e728..48751fe34c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -120,7 +120,7 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener searchMode = 0; deepSearchTV.setText( String.format( - "%s%s", + "%s %s", mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_indexed_search))); @@ -158,7 +158,12 @@ public void afterTextChanged(Editable s) {} searchViewEditText.setOnEditorActionListener( (v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_SEARCH) return onSearch(true); + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + + Utils.hideKeyboard(mainActivity); + + return onSearch(true); + } return false; }); @@ -176,7 +181,7 @@ public void afterTextChanged(Editable s) {} searchMode = 2; deepSearchTV.setText( String.format( - "%s%s", + "%s %s", mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_recursive_search))); @@ -216,7 +221,7 @@ private void search(String s) { searchMode = 1; deepSearchTV.setText( String.format( - "%s%s", + "%s %s", mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_indexed_search))); @@ -306,6 +311,9 @@ private void initRecentSearches(Context context) { String s = ((Chip) v).getText().toString(); searchViewEditText.setText(s); + + Utils.hideKeyboard(mainActivity); + search(s); }); } diff --git a/app/src/main/java/com/amaze/filemanager/utils/Utils.java b/app/src/main/java/com/amaze/filemanager/utils/Utils.java index 009e25d58b..8c7f0d44be 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/Utils.java +++ b/app/src/main/java/com/amaze/filemanager/utils/Utils.java @@ -50,6 +50,7 @@ import android.text.format.DateUtils; import android.util.DisplayMetrics; import android.view.View; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; @@ -456,4 +457,11 @@ public static void addShortcut( ShortcutManagerCompat.requestPinShortcut(context, info, null); } + + public static void hideKeyboard(MainActivity mainActivity) { + View view = mainActivity.getCurrentFocus(); + if (view != null) + ((InputMethodManager) mainActivity.getSystemService(Context.INPUT_METHOD_SERVICE)) + .hideSoftInputFromWindow(view.getWindowToken(), 0); + } } diff --git a/app/src/main/res/layout-v21/layout_search.xml b/app/src/main/res/layout-v21/layout_search.xml index eda8994a46..ed8aaa8dcb 100644 --- a/app/src/main/res/layout-v21/layout_search.xml +++ b/app/src/main/res/layout-v21/layout_search.xml @@ -33,7 +33,7 @@ + + + app:layout_constraintTop_toBottomOf="@id/searchResultsHintTV" /> + + + app:layout_constraintTop_toBottomOf="@id/searchResultsHintTV" /> Not finding what you\'re looking for? Try Recursive Search! Try Indexed Search! + Recent + Results From f7dccf8b7c0280e006a06767d2aec31a14f3ad6b Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 15:12:34 +0530 Subject: [PATCH 10/58] recursive search MVP --- .../com/amaze/filemanager/ui/views/appbar/SearchView.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 48751fe34c..4dbc7a7c93 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -170,10 +170,11 @@ public void afterTextChanged(Editable s) {} deepSearchTV.setOnClickListener( v -> { + String s = searchViewEditText.getText().toString().trim(); + if (searchMode == 1) { - List hybridFileParcelables = - indexedSearch(mainActivity, searchViewEditText.getText().toString().trim()); + List hybridFileParcelables = indexedSearch(mainActivity, s); searchRecyclerViewAdapter.submitList(hybridFileParcelables); searchRecyclerViewAdapter.notifyDataSetChanged(); @@ -187,6 +188,9 @@ public void afterTextChanged(Editable s) {} } else if (searchMode == 2) { + searchListener.onSearch(s); + appbar.getSearchView().hideSearchView(); + deepSearchTV.setVisibility(View.GONE); } }); From ce8bfbd13aee7bc288aec3b3eae2a8cf4a4a0867 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 15:33:16 +0530 Subject: [PATCH 11/58] UI --- .../adapters/SearchRecyclerViewAdapter.kt | 35 ++++++++++++------- .../ui/views/appbar/SearchView.java | 1 + 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index c3f233ceb5..ba44316631 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -65,18 +65,27 @@ class SearchRecyclerViewAdapter : val item = getItem(position) holder.fileNameTV.text = item.name + holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) + +// val colorPreference = +// (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference +// +// if (item != null && item.isDirectory) { // always null for some reason! +// holder.colorView.setBackgroundColor(colorPreference.iconSkin) +// } else { +// holder.colorView.setBackgroundColor(colorPreference.accent) +// } } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val fileNameTV: TextView + val colorView: View init { fileNameTV = view.findViewById(R.id.searchItemFileNameTV) - - view.findViewById(R.id.searchItemSampleColorView) - .setBackgroundColor(getRandomColor(view.context)) + colorView = view.findViewById(R.id.searchItemSampleColorView) view.setOnClickListener { @@ -96,16 +105,16 @@ class SearchRecyclerViewAdapter : ?.appbar?.searchView?.hideSearchView() } } + } - private fun getRandomColor(context: Context): Int { - return ContextCompat.getColor( - context, - ColorPreference.availableColors[ - Random().nextInt( - ColorPreference.availableColors.size - 1 - ) - ] - ) - } + private fun getRandomColor(context: Context): Int { + return ContextCompat.getColor( + context, + ColorPreference.availableColors[ + Random().nextInt( + ColorPreference.availableColors.size - 1 + ) + ] + ) } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 4dbc7a7c93..525f7a45ce 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -123,6 +123,7 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener "%s %s", mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_indexed_search))); + deepSearchTV.setVisibility(View.GONE); initRecentSearches(mainActivity); From 8c097529b42b53c42c0560a0c606da0bf1e6e23a Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 15:35:42 +0530 Subject: [PATCH 12/58] fix bug in indexed search --- .../java/com/amaze/filemanager/ui/views/appbar/SearchView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 525f7a45ce..3f71245056 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -342,7 +342,7 @@ else if (cursor.getCount() > 0 && cursor.moveToFirst()) { if (path != null && path.contains(mainActivity.getCurrentMainFragment().getPath()) - && path.toLowerCase().contains(query.toLowerCase())) { + && new File(path).getName().toLowerCase().contains(query.toLowerCase())) { boolean showHiddenFiles = PreferenceManager.getDefaultSharedPreferences(mainActivity) From 389dc54e2a60b96709845494453a5e687aafbc4c Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 15:47:15 +0530 Subject: [PATCH 13/58] add file path to results --- .../adapters/SearchRecyclerViewAdapter.kt | 6 ++++- app/src/main/res/layout/search_row_item.xml | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index ba44316631..ad17c6caa9 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -65,12 +65,14 @@ class SearchRecyclerViewAdapter : val item = getItem(position) holder.fileNameTV.text = item.name + holder.filePathTV.text = item.path.substring(0, item.path.lastIndexOf("/")) + holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) // val colorPreference = // (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference // -// if (item != null && item.isDirectory) { // always null for some reason! +// if (item != null && item.isDirectory) { // always false for some reason! // holder.colorView.setBackgroundColor(colorPreference.iconSkin) // } else { // holder.colorView.setBackgroundColor(colorPreference.accent) @@ -80,11 +82,13 @@ class SearchRecyclerViewAdapter : inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val fileNameTV: TextView + val filePathTV: TextView val colorView: View init { fileNameTV = view.findViewById(R.id.searchItemFileNameTV) + filePathTV = view.findViewById(R.id.searchItemFilePathTV) colorView = view.findViewById(R.id.searchItemSampleColorView) view.setOnClickListener { diff --git a/app/src/main/res/layout/search_row_item.xml b/app/src/main/res/layout/search_row_item.xml index 2069032c96..0ff8072465 100644 --- a/app/src/main/res/layout/search_row_item.xml +++ b/app/src/main/res/layout/search_row_item.xml @@ -13,7 +13,7 @@ + + \ No newline at end of file From 0e2b2373102db57ecf126cf5c6027b37549478f4 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 20:44:17 +0530 Subject: [PATCH 14/58] codacy: These nested if statements could be combined --- .../filemanager/ui/views/appbar/SearchView.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 3f71245056..bdbeffe16a 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -243,17 +243,15 @@ private void search(String s) { showHiddenFiles, mode -> null, hybridFileParcelable -> { - if (showHiddenFiles || !hybridFileParcelable.isHidden()) - if (hybridFileParcelable - .getName(mainActivity) - .toLowerCase() - .contains(s.toLowerCase())) { - hybridFileParcelables.add(hybridFileParcelable); + if (hybridFileParcelable.getName(mainActivity).toLowerCase().contains(s.toLowerCase()) + && (showHiddenFiles || !hybridFileParcelable.isHidden())) { - searchRecyclerViewAdapter.submitList(hybridFileParcelables); + hybridFileParcelables.add(hybridFileParcelable); - searchRecyclerViewAdapter.notifyItemInserted(hybridFileParcelables.size() + 1); - } + searchRecyclerViewAdapter.submitList(hybridFileParcelables); + + searchRecyclerViewAdapter.notifyItemInserted(hybridFileParcelables.size() + 1); + } return null; }); } From 9db1d72eaedac11dae314135b84a87825bddcd2f Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 26 May 2023 20:45:35 +0530 Subject: [PATCH 15/58] codacy: Avoid unused private fields such as 'searchListener'. --- .../java/com/amaze/filemanager/ui/views/appbar/SearchView.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index bdbeffe16a..f9a2b3e1a3 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -89,7 +89,6 @@ public class SearchView { private final ChipGroup recentChipGroup; private final RecyclerView recyclerView; - private final SearchListener searchListener; private final SearchRecyclerViewAdapter searchRecyclerViewAdapter; // 0 -> Basic @@ -104,7 +103,6 @@ public class SearchView { public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener searchListener) { this.mainActivity = mainActivity; - this.searchListener = searchListener; this.appbar = appbar; searchViewLayout = mainActivity.findViewById(R.id.search_view); From 5eeef32dbb9cf4854296943e85e2f0f7185dffc4 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sat, 24 Sep 2022 17:39:48 +0800 Subject: [PATCH 16/58] Subnetscanner v2 Fixes #2622 and fixes #3386. --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 3 +- .../asynchronous/services/ftp/FtpService.kt | 110 +----- .../services/ftp/FtpTileService.java | 5 +- .../filemanager/filesystem/FileUtil.java | 2 +- .../filemanager/filesystem/HybridFile.java | 2 +- .../filesystem/ftp/NetCopyClientUtils.kt | 2 +- .../ui/dialogs/SmbConnectDialog.java | 14 +- .../ui/dialogs/SmbSearchDialog.java | 236 ------------- .../filemanager/ui/dialogs/SmbSearchDialog.kt | 256 ++++++++++++++ .../ui/fragments/CloudSheetFragment.java | 2 +- .../ui/fragments/FtpServerFragment.kt | 6 +- .../ui/notifications/FtpNotification.java | 3 +- .../filemanager/utils/ComputerParcelable.kt | 7 +- .../filemanager/utils/MainActivityHelper.java | 1 + .../amaze/filemanager/utils/NetworkUtil.kt | 151 +++++++++ .../filemanager/utils/SubnetScanner.java | 252 -------------- .../com/amaze/filemanager/utils/UUIDv5.kt | 94 ++++++ .../smb/SameSubnetDiscoverDeviceStrategy.kt | 102 ++++++ .../utils/smb/SmbDeviceScannerObservable.kt | 96 ++++++ .../filemanager/utils/{ => smb}/SmbUtil.kt | 4 +- .../utils/smb/WsddDiscoverDeviceStrategy.kt | 294 ++++++++++++++++ app/src/main/res/raw/wsd_request.txt | 22 ++ app/src/main/res/raw/wsdd_discovery.txt | 1 + .../asynctasks/SmbDeleteTaskTest.kt | 2 +- .../UtilitiesDatabaseMigrationTest.kt | 2 +- .../filemanager/database/UtilsHandlerTest.kt | 2 +- .../utils/ComputerParcelableTest.java | 6 +- .../amaze/filemanager/utils/SmbUtilTest.kt | 8 +- .../com/amaze/filemanager/utils/UUIDv5Test.kt | 43 +++ ...tractSubnetDiscoverDevicesStrategyTests.kt | 75 +++++ .../SameSubnetDiscoverDevicesStrategyTest.kt | 58 ++++ .../WsddSubnetDiscoverDevicesStrategyTest.kt | 156 +++++++++ .../resources/wsdd/multicast-response.txt | 28 ++ app/src/test/resources/wsdd/wsd-response.txt | 46 +++ build.gradle | 4 +- portscanner/.gitignore | 1 + portscanner/build.gradle | 37 ++ portscanner/consumer-rules.pro | 0 portscanner/proguard-rules.pro | 21 ++ portscanner/src/main/AndroidManifest.xml | 8 + .../com/stealthcopter/networktools/IPTools.kt | 130 ++++++++ .../stealthcopter/networktools/PortScan.kt | 315 ++++++++++++++++++ .../networktools/portscanning/PortScanTCP.kt | 50 +++ .../networktools/portscanning/PortScanUDP.kt | 57 ++++ settings.gradle | 1 + .../filemanager/shadows/ShadowSmbUtil.kt | 2 +- .../filemanager/test/volley/MockHttpStack.kt | 51 +++ 48 files changed, 2141 insertions(+), 633 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.java create mode 100644 app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt create mode 100644 app/src/main/java/com/amaze/filemanager/utils/NetworkUtil.kt delete mode 100644 app/src/main/java/com/amaze/filemanager/utils/SubnetScanner.java create mode 100644 app/src/main/java/com/amaze/filemanager/utils/UUIDv5.kt create mode 100644 app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt create mode 100644 app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt rename app/src/main/java/com/amaze/filemanager/utils/{ => smb}/SmbUtil.kt (98%) create mode 100644 app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt create mode 100644 app/src/main/res/raw/wsd_request.txt create mode 100644 app/src/main/res/raw/wsdd_discovery.txt create mode 100644 app/src/test/java/com/amaze/filemanager/utils/UUIDv5Test.kt create mode 100644 app/src/test/java/com/amaze/filemanager/utils/smb/AbstractSubnetDiscoverDevicesStrategyTests.kt create mode 100644 app/src/test/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDevicesStrategyTest.kt create mode 100644 app/src/test/java/com/amaze/filemanager/utils/smb/WsddSubnetDiscoverDevicesStrategyTest.kt create mode 100644 app/src/test/resources/wsdd/multicast-response.txt create mode 100644 app/src/test/resources/wsdd/wsd-response.txt create mode 100644 portscanner/.gitignore create mode 100644 portscanner/build.gradle create mode 100644 portscanner/consumer-rules.pro create mode 100644 portscanner/proguard-rules.pro create mode 100644 portscanner/src/main/AndroidManifest.xml create mode 100644 portscanner/src/main/java/com/stealthcopter/networktools/IPTools.kt create mode 100644 portscanner/src/main/java/com/stealthcopter/networktools/PortScan.kt create mode 100644 portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanTCP.kt create mode 100644 portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanUDP.kt create mode 100644 testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt diff --git a/app/build.gradle b/app/build.gradle index 34cd3e4b97..2e1f2660f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,7 +133,6 @@ dependencies { implementation "androidx.room:room-rxjava2:$roomVersion" kapt "androidx.annotation:annotation:$androidXAnnotationVersion" - implementation "androidx.preference:preference:$androidXPrefVersion" implementation "androidx.preference:preference-ktx:$androidXPrefVersion" //For tests @@ -233,14 +232,15 @@ dependencies { implementation 'org.tukaani:xz:1.9' - implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version) - implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.9' + implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" implementation project(':commons_compress_7z') implementation project(':file_operations') + implementation project(':portscanner') implementation "androidx.core:core-ktx:$androidXCoreVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "ch.acra:acra-core:5.7.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3eb63f4c86..96ff20b8a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,7 +54,8 @@ tools:replace="android:label" android:label="@string/appbar_name" android:requestLegacyExternalStorage="true" - android:banner="@drawable/about_header"> + android:banner="@drawable/about_header" + android:usesCleartextTraffic="true"> = M) { - connected = cm.activeNetwork?.let { activeNetwork -> - cm.getNetworkCapabilities(activeNetwork)?.let { ni -> - ni.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) or - ni.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) - } ?: false - } ?: false - } else { - connected = cm.activeNetworkInfo?.let { ni -> - ni.isConnected && ( - ni.type and ( - ConnectivityManager.TYPE_WIFI - or ConnectivityManager.TYPE_ETHERNET - ) != 0 - ) - } ?: false - } - - if (!connected) { - connected = runCatching { - NetworkInterface.getNetworkInterfaces().toList().find { netInterface -> - netInterface.displayName.startsWith("rndis") or - netInterface.displayName.startsWith("wlan") - } - }.getOrElse { null } != null - } - - return connected - } - - /** - * Is the device connected to Wifi? - */ - @JvmStatic - fun isConnectedToWifi(context: Context): Boolean { - val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - return if (SDK_INT >= M) { - cm.activeNetwork?.let { - cm.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) - } ?: false - } else { - cm.activeNetworkInfo?.let { - it.isConnected && it.type == ConnectivityManager.TYPE_WIFI - } ?: false - } - } - - /** - * Determine device's IP address - */ - @JvmStatic - fun getLocalInetAddress(context: Context): InetAddress? { - if (!isConnectedToLocalNetwork(context)) { - return null - } - if (isConnectedToWifi(context)) { - val wm = context.applicationContext.getSystemService(WIFI_SERVICE) as WifiManager - val ipAddress = wm.connectionInfo.ipAddress - return if (ipAddress == 0) null else intToInet(ipAddress) - } - runCatching { - NetworkInterface.getNetworkInterfaces().iterator().forEach { netinterface -> - netinterface.inetAddresses.iterator().forEach { address -> - // this is the condition that sometimes gives problems - if (!address.isLoopbackAddress && - !address.isLinkLocalAddress - ) { - return address - } - } - } - }.onFailure { e -> - log.warn("failed to get local inet address", e) - } - return null - } - - private fun intToInet(value: Int): InetAddress? { - val bytes = ByteArray(4) - for (i in 0..3) { - bytes[i] = byteOfInt(value, i) - } - return try { - InetAddress.getByAddress(bytes) - } catch (e: UnknownHostException) { - // This only happens if the byte array has a bad length - null - } - } - - private fun byteOfInt(value: Int, which: Int): Byte { - val shift = which * 8 - return (value shr shift).toByte() - } - private fun getPort(preferences: SharedPreferences): Int { - return preferences.getInt(PORT_PREFERENCE_KEY, DEFAULT_PORT) + return preferences.getInt(FtpService.PORT_PREFERENCE_KEY, FtpService.DEFAULT_PORT) } } } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java index c9c44158f8..01e74981b3 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java @@ -24,6 +24,7 @@ import org.greenrobot.eventbus.Subscribe; import com.amaze.filemanager.R; +import com.amaze.filemanager.utils.NetworkUtil; import android.annotation.TargetApi; import android.content.Intent; @@ -64,8 +65,8 @@ public void onClick() { .sendBroadcast( new Intent(FtpService.ACTION_STOP_FTPSERVER).setPackage(getPackageName())); } else { - if (FtpService.isConnectedToWifi(getApplicationContext()) - || FtpService.isConnectedToLocalNetwork(getApplicationContext())) { + if (NetworkUtil.isConnectedToWifi(getApplicationContext()) + || NetworkUtil.isConnectedToLocalNetwork(getApplicationContext())) { Intent i = new Intent(FtpService.ACTION_START_FTPSERVER).setPackage(getPackageName()); i.putExtra(FtpService.TAG_STARTED_BY_TILE, true); getApplicationContext().sendBroadcast(i); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java b/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java index cd4bdec12b..3d129a9fdc 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/FileUtil.java @@ -43,7 +43,7 @@ import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.utils.DataUtils; import com.amaze.filemanager.utils.OTGUtil; -import com.amaze.filemanager.utils.SmbUtil; +import com.amaze.filemanager.utils.smb.SmbUtil; import com.cloudrail.si.interfaces.CloudStorage; import android.content.ContentResolver; 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 8f55be3882..2f91894d0d 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/HybridFile.java @@ -85,8 +85,8 @@ import com.amaze.filemanager.utils.Function; import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.OnFileFound; -import com.amaze.filemanager.utils.SmbUtil; import com.amaze.filemanager.utils.Utils; +import com.amaze.filemanager.utils.smb.SmbUtil; import com.cloudrail.si.interfaces.CloudStorage; import com.cloudrail.si.types.SpaceAllocation; diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt index 372bc5279f..3fedf1b0eb 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt @@ -38,7 +38,7 @@ import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.COLO import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.SLASH import com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX import com.amaze.filemanager.filesystem.ssh.SFtpClientTemplate -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import com.amaze.filemanager.utils.urlEncoded import io.reactivex.Maybe import io.reactivex.Scheduler diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java index 50f30fd136..b05b08e259 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java @@ -25,7 +25,7 @@ import static com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.COLON; import static com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.SLASH; import static com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX; -import static com.amaze.filemanager.utils.SmbUtil.PARAM_DISABLE_IPC_SIGNING_CHECK; +import static com.amaze.filemanager.utils.smb.SmbUtil.PARAM_DISABLE_IPC_SIGNING_CHECK; import static java.net.URLDecoder.decode; import static java.net.URLEncoder.encode; @@ -49,8 +49,8 @@ import com.amaze.filemanager.utils.EditTextColorStateUtil; import com.amaze.filemanager.utils.PasswordUtil; import com.amaze.filemanager.utils.SimpleTextWatcher; -import com.amaze.filemanager.utils.SmbUtil; import com.amaze.filemanager.utils.Utils; +import com.amaze.filemanager.utils.smb.SmbUtil; import com.google.android.material.textfield.TextInputLayout; import android.app.Dialog; @@ -227,15 +227,15 @@ public void afterTextChanged(@NonNull Editable s) { final AppCompatCheckBox chkSmbDisableIpcSignature = binding.chkSmbDisableIpcSignature; TextView help = binding.wanthelp; - EditTextColorStateUtil.setTint(context, conName, accentColor); - EditTextColorStateUtil.setTint(context, user, accentColor); - EditTextColorStateUtil.setTint(context, pass, accentColor); + EditTextColorStateUtil.setTint(getActivity(), conName, accentColor); + EditTextColorStateUtil.setTint(getActivity(), user, accentColor); + EditTextColorStateUtil.setTint(getActivity(), pass, accentColor); - Utils.setTint(context, chkSmbAnonymous, accentColor); + Utils.setTint(getActivity(), chkSmbAnonymous, accentColor); help.setOnClickListener( v -> { int accentColor1 = ((ThemedActivity) getActivity()).getAccent(); - GeneralDialogCreation.showSMBHelpDialog(context, accentColor1); + GeneralDialogCreation.showSMBHelpDialog(getActivity(), accentColor1); }); chkSmbAnonymous.setOnClickListener( diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.java deleted file mode 100644 index ee0626834f..0000000000 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.ui.dialogs; - -import java.util.ArrayList; -import java.util.List; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.amaze.filemanager.R; -import com.amaze.filemanager.ui.activities.MainActivity; -import com.amaze.filemanager.ui.activities.superclasses.BasicActivity; -import com.amaze.filemanager.ui.activities.superclasses.ThemedActivity; -import com.amaze.filemanager.ui.provider.UtilitiesProvider; -import com.amaze.filemanager.ui.theme.AppTheme; -import com.amaze.filemanager.utils.ComputerParcelable; -import com.amaze.filemanager.utils.SubnetScanner; - -import android.app.Activity; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.Context; -import android.graphics.Color; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -/** Created by arpitkh996 on 16-01-2016 edited by Emmanuel Messulam */ -public class SmbSearchDialog extends DialogFragment { - private UtilitiesProvider utilsProvider; - - private ListViewAdapter listViewAdapter; - private ArrayList computers = new ArrayList<>(); - private int accentColor; - private SubnetScanner subnetScanner; - - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - utilsProvider = ((BasicActivity) getActivity()).getUtilsProvider(); - - accentColor = ((ThemedActivity) getActivity()).getAccent(); - } - - @Override - public void dismiss() { - super.dismiss(); - if (subnetScanner != null) subnetScanner.cancel(true); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - MaterialDialog.Builder builder = new MaterialDialog.Builder(getActivity()); - builder.title(R.string.searching_devices); - builder.negativeColor(accentColor); - builder.negativeText(R.string.cancel); - builder.onNegative( - (dialog, which) -> { - if (subnetScanner != null) subnetScanner.cancel(true); - dismiss(); - }); - builder.onPositive( - (dialog, which) -> { - if (subnetScanner != null) subnetScanner.cancel(true); - if (getActivity() != null && getActivity() instanceof MainActivity) { - dismiss(); - MainActivity mainActivity = (MainActivity) getActivity(); - mainActivity.showSMBDialog("", "", false); - } - }); - builder.positiveText(R.string.use_custom_ip); - builder.positiveColor(accentColor); - computers.add(new ComputerParcelable("-1", "-1")); - listViewAdapter = new ListViewAdapter(getActivity(), computers); - subnetScanner = new SubnetScanner(getActivity()); - subnetScanner.setObserver( - new SubnetScanner.ScanObserver() { - @Override - public void computerFound(final ComputerParcelable computer) { - if (getActivity() != null) - getActivity() - .runOnUiThread( - () -> { - if (!computers.contains(computer)) computers.add(computer); - listViewAdapter.notifyDataSetChanged(); - }); - } - - @Override - public void searchFinished() { - if (getActivity() != null) { - getActivity() - .runOnUiThread( - () -> { - if (computers.size() == 1) { - dismiss(); - Toast.makeText( - getActivity(), - getString(R.string.no_device_found), - Toast.LENGTH_SHORT) - .show(); - MainActivity mainActivity = (MainActivity) getActivity(); - mainActivity.showSMBDialog("", "", false); - return; - } - computers.remove(computers.size() - 1); - listViewAdapter.notifyDataSetChanged(); - }); - } - } - }); - subnetScanner.execute(); - - builder.adapter(listViewAdapter, null); - return builder.build(); - } - - private class ListViewAdapter extends RecyclerView.Adapter { - private static final int VIEW_PROGRESSBAR = 1; - private static final int VIEW_ELEMENT = 2; - - private ArrayList items; - private LayoutInflater mInflater; - - ListViewAdapter(Context context, List objects) { - items = new ArrayList<>(objects); - mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - } - - @Override - @NonNull - public ElementViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view; - switch (viewType) { - case VIEW_PROGRESSBAR: - view = mInflater.inflate(R.layout.smb_progress_row, parent, false); - return new ElementViewHolder(view); - default: - case VIEW_ELEMENT: - view = mInflater.inflate(R.layout.smb_computers_row, parent, false); - return new ElementViewHolder(view); - } - } - - @Override - public void onBindViewHolder(@NonNull ElementViewHolder holder, int position) { - int viewType = getItemViewType(position); - if (viewType == VIEW_PROGRESSBAR) { - return; - } - - ComputerParcelable f = items.get(position); - - holder.rootView.setOnClickListener( - v -> { - if (subnetScanner != null) subnetScanner.cancel(true); - if (getActivity() != null && getActivity() instanceof MainActivity) { - dismiss(); - MainActivity mainActivity = (MainActivity) getActivity(); - mainActivity.showSMBDialog( - listViewAdapter.items.get(position).getName(), - listViewAdapter.items.get(position).getAddr(), - false); - } - }); - - holder.txtTitle.setText(f.getName()); - holder.image.setImageResource(R.drawable.ic_settings_remote_white_48dp); - if (utilsProvider.getAppTheme().equals(AppTheme.LIGHT)) - holder.image.setColorFilter(Color.parseColor("#666666")); - holder.txtDesc.setText(f.getAddr()); - } - - @Override - public int getItemViewType(int position) { - ComputerParcelable f = items.get(position); - if (f.getAddr().equals("-1")) { - return VIEW_PROGRESSBAR; - } else { - return VIEW_ELEMENT; - } - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getItemCount() { - return items.size(); - } - } - - private static class ElementViewHolder extends RecyclerView.ViewHolder { - private View rootView; - - private ImageView image; - private TextView txtTitle; - private TextView txtDesc; - - ElementViewHolder(View view) { - super(view); - - rootView = view; - - txtTitle = view.findViewById(R.id.firstline); - image = view.findViewById(R.id.icon); - txtDesc = view.findViewById(R.id.secondLine); - } - } -} diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt new file mode 100644 index 0000000000..bd92520db8 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.ui.dialogs + +import android.app.Activity +import android.app.Dialog +import android.content.Context +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.recyclerview.widget.RecyclerView +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog +import com.amaze.filemanager.R +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.ui.activities.MainActivity +import com.amaze.filemanager.ui.activities.superclasses.ThemedActivity +import com.amaze.filemanager.ui.provider.UtilitiesProvider +import com.amaze.filemanager.ui.theme.AppTheme +import com.amaze.filemanager.utils.ComputerParcelable +import com.amaze.filemanager.utils.smb.SmbDeviceScannerObservable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import org.slf4j.LoggerFactory + +/** Created by arpitkh996 on 16-01-2016 edited by Emmanuel Messulam @gmail.com> */ +class SmbSearchDialog : DialogFragment() { + + private lateinit var utilsProvider: UtilitiesProvider + private lateinit var listViewAdapter: ListViewAdapter + private val viewModel = ComputerParcelableViewModel() + private var accentColor = 0 + private lateinit var subnetScannerObserver: Disposable + + override fun onCreate(bundle: Bundle?) { + super.onCreate(bundle) + utilsProvider = AppConfig.getInstance().utilsProvider + accentColor = (activity as ThemedActivity).accent + } + + override fun dismiss() { + super.dismiss() + if (!subnetScannerObserver.isDisposed) { + subnetScannerObserver.dispose() + } + } + + @Suppress("LongMethod", "LabeledExpression") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val builder = MaterialDialog.Builder(requireActivity()) + builder.title(R.string.searching_devices) + builder.negativeColor(accentColor) + builder.negativeText(R.string.cancel) + builder.onNegative { _: MaterialDialog?, _: DialogAction? -> + if (!subnetScannerObserver.isDisposed) { + subnetScannerObserver.dispose() + } + dismiss() + } + builder.onPositive { _: MaterialDialog?, _: DialogAction? -> + if (!subnetScannerObserver.isDisposed) { + subnetScannerObserver.dispose() + } + if (activity != null && activity is MainActivity) { + dismiss() + val mainActivity = activity as MainActivity + mainActivity.showSMBDialog("", "", false) + } + } + builder.positiveText(R.string.use_custom_ip) + builder.positiveColor(accentColor) + viewModel.valHolder.value = (ComputerParcelable("-1", "-1")) + listViewAdapter = ListViewAdapter(requireActivity()) + val observable = SmbDeviceScannerObservable() + subnetScannerObserver = observable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnDispose { + observable.stop() + } + .subscribe( + { computer: ComputerParcelable -> + if (!listViewAdapter.contains(computer)) { + viewModel.valHolder.value = computer + } + }, + { err: Throwable -> + LOG.error("Error searching for devices", err) + } + ) { + subnetScannerObserver.dispose() + activity?.runOnUiThread { + if (listViewAdapter.dummyOnly()) { + dismiss() + Toast.makeText( + activity, + getString(R.string.no_device_found), + Toast.LENGTH_SHORT + ).show() + val mainActivity = activity as MainActivity + mainActivity.showSMBDialog("", "", false) + return@runOnUiThread + } + listViewAdapter.removeDummy() + } + } + builder.adapter(listViewAdapter, null) + viewModel.valHolder.observe(this) { + listViewAdapter.add(it) + } + return builder.build() + } + + private inner class ListViewAdapter( + context: Context + ) : RecyclerView.Adapter() { + private val items: MutableList = ArrayList() + private val mInflater: LayoutInflater + + init { + mInflater = context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater + } + + /** + * Called by [ComputerParcelableViewModel], add found computer to list view + */ + fun add(computer: ComputerParcelable) { + items.add(computer) + notifyDataSetChanged() + } + + /** + * Called by Observable when finish probing. If no other computers found, remove first + * (dummy) host + */ + fun removeDummy() { + items.removeFirst() + notifyDataSetChanged() + } + + /** + * Answers if the computer list contains given instance. + */ + fun contains(computer: ComputerParcelable): Boolean { + return items.contains(computer) + } + + /** + * Answers if the list is empty = only has the dummy [ComputerParcelable] instance + */ + fun dummyOnly(): Boolean { + return items.size == 1 && items.first().addr == "-1" + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view: View + return when (viewType) { + VIEW_PROGRESSBAR -> { + view = mInflater.inflate(R.layout.smb_progress_row, parent, false) + ViewHolder(view) + } + else -> { + view = + mInflater.inflate(R.layout.smb_computers_row, parent, false) + ElementViewHolder(view) + } + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val viewType = getItemViewType(position) + if (viewType == Companion.VIEW_PROGRESSBAR) { + return + } + val (addr, name) = items[position] + holder.rootView.setOnClickListener { + if (activity != null && activity is MainActivity) { + dismiss() + val mainActivity = activity as MainActivity + mainActivity.showSMBDialog( + listViewAdapter.items[position].name, + listViewAdapter.items[position].addr, + false + ) + } + } + if (holder is ElementViewHolder) { + holder.txtTitle.text = name + holder.image.setImageResource(R.drawable.ic_settings_remote_white_48dp) + if (utilsProvider.appTheme == AppTheme.LIGHT) { + holder.image.setColorFilter(Color.parseColor("#666666")) + } + holder.txtDesc.text = addr + } + } + + override fun getItemViewType(position: Int): Int { + val (addr) = items[position] + return if (addr == "-1") { + VIEW_PROGRESSBAR + } else { + VIEW_ELEMENT + } + } + + override fun getItemId(position: Int): Long = position.toLong() + + override fun getItemCount(): Int = items.size + } + + private open class ViewHolder(val rootView: View) : RecyclerView.ViewHolder(rootView) + + private class ElementViewHolder(rootView: View) : + ViewHolder(rootView) { + val image: ImageView = rootView.findViewById(R.id.icon) + val txtTitle: TextView = rootView.findViewById(R.id.firstline) + val txtDesc: TextView = rootView.findViewById(R.id.secondLine) + } + + private class ComputerParcelableViewModel : ViewModel() { + val valHolder = MutableLiveData() + } + + companion object { + private const val VIEW_PROGRESSBAR = 1 + private const val VIEW_ELEMENT = 2 + private val LOG = LoggerFactory.getLogger(SmbSearchDialog::class.java) + } +} diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/CloudSheetFragment.java b/app/src/main/java/com/amaze/filemanager/ui/fragments/CloudSheetFragment.java index 648abc8b3c..0037e97a49 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/CloudSheetFragment.java +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/CloudSheetFragment.java @@ -159,7 +159,7 @@ public void onClick(View v) { case R.id.linear_layout_smb: dismiss(); SmbSearchDialog smbDialog = new SmbSearchDialog(); - smbDialog.show(getActivity().getFragmentManager(), "tab"); + smbDialog.show(getActivity().getSupportFragmentManager(), "tab"); return; case R.id.linear_layout_scp: dismiss(); 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 31545e7b88..9cde4e5cc7 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 @@ -68,9 +68,6 @@ import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.asynchronous.services.ftp.FtpService import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.KEY_PREFERENCE_PATH import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.KEY_PREFERENCE_ROOT_FILESYSTEM -import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.getLocalInetAddress -import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isConnectedToLocalNetwork -import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isConnectedToWifi import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isRunning import com.amaze.filemanager.asynchronous.services.ftp.FtpService.FtpReceiverActions import com.amaze.filemanager.databinding.DialogFtpLoginBinding @@ -80,6 +77,9 @@ 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.NetworkUtil.getLocalInetAddress +import com.amaze.filemanager.utils.NetworkUtil.isConnectedToLocalNetwork +import com.amaze.filemanager.utils.NetworkUtil.isConnectedToWifi import com.amaze.filemanager.utils.OneCharacterCharSequence import com.amaze.filemanager.utils.PasswordUtil import com.amaze.filemanager.utils.Utils diff --git a/app/src/main/java/com/amaze/filemanager/ui/notifications/FtpNotification.java b/app/src/main/java/com/amaze/filemanager/ui/notifications/FtpNotification.java index d803267ff7..defb81ff26 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/notifications/FtpNotification.java +++ b/app/src/main/java/com/amaze/filemanager/ui/notifications/FtpNotification.java @@ -28,6 +28,7 @@ import com.amaze.filemanager.R; import com.amaze.filemanager.asynchronous.services.ftp.FtpService; import com.amaze.filemanager.ui.activities.MainActivity; +import com.amaze.filemanager.utils.NetworkUtil; import android.app.Notification; import android.app.NotificationManager; @@ -104,7 +105,7 @@ public static void updateNotification(Context context, boolean noStopButton) { boolean secureConnection = sharedPreferences.getBoolean(FtpService.KEY_PREFERENCE_SECURE, FtpService.DEFAULT_SECURE); - InetAddress address = FtpService.getLocalInetAddress(context); + InetAddress address = NetworkUtil.getLocalInetAddress(context); String address_text = "Address not found"; diff --git a/app/src/main/java/com/amaze/filemanager/utils/ComputerParcelable.kt b/app/src/main/java/com/amaze/filemanager/utils/ComputerParcelable.kt index b5358507f1..4b8a6eeac6 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/ComputerParcelable.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/ComputerParcelable.kt @@ -24,6 +24,9 @@ import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize -data class ComputerParcelable(val addr: String?, val name: String?) : Parcelable { - override fun toString(): String = String.format("%s [%s]", name, addr) +data class ComputerParcelable(val addr: String, val name: String) : Parcelable { + override fun toString(): String = "$name [$addr]" + override fun hashCode(): Int = addr.hashCode() + override fun equals(other: Any?): Boolean = + other is ComputerParcelable && other.addr == this.addr } 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 53a029c69e..9caba5adb9 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java +++ b/app/src/main/java/com/amaze/filemanager/utils/MainActivityHelper.java @@ -66,6 +66,7 @@ import com.amaze.filemanager.ui.fragments.TabFragment; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.views.WarnableTextInputValidator; +import com.amaze.filemanager.utils.smb.SmbUtil; import com.leinardi.android.speeddial.SpeedDialView; import android.annotation.SuppressLint; diff --git a/app/src/main/java/com/amaze/filemanager/utils/NetworkUtil.kt b/app/src/main/java/com/amaze/filemanager/utils/NetworkUtil.kt new file mode 100644 index 0000000000..8bf5b4b4d6 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/utils/NetworkUtil.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils + +import android.app.Service +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.net.wifi.WifiManager +import android.os.Build +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.net.InetAddress +import java.net.NetworkInterface +import java.net.UnknownHostException + +object NetworkUtil { + + private val log: Logger = LoggerFactory.getLogger(NetworkUtil::class.java) + + private fun getConnectivityManager(context: Context) = + context.applicationContext.getSystemService(Service.CONNECTIVITY_SERVICE) + as ConnectivityManager + + /** + * Is the device connected to local network, either Ethernet or Wifi? + */ + @JvmStatic + fun isConnectedToLocalNetwork(context: Context): Boolean { + val cm = getConnectivityManager(context) + var connected: Boolean + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + connected = cm.activeNetwork?.let { activeNetwork -> + cm.getNetworkCapabilities(activeNetwork)?.let { ni -> + ni.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) or + ni.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + } ?: false + } ?: false + } else { + connected = cm.activeNetworkInfo?.let { ni -> + ni.isConnected && ( + ni.type and ( + ConnectivityManager.TYPE_WIFI + or ConnectivityManager.TYPE_ETHERNET + ) != 0 + ) + } ?: false + } + + if (!connected) { + connected = runCatching { + NetworkInterface.getNetworkInterfaces().toList().find { netInterface -> + netInterface.displayName.startsWith("rndis") or + netInterface.displayName.startsWith("wlan") + } + }.getOrElse { null } != null + } + + return connected + } + + /** + * Is the device connected to Wifi? + */ + @JvmStatic + fun isConnectedToWifi(context: Context): Boolean { + val cm = getConnectivityManager(context) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + cm.activeNetwork?.let { + cm.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + } ?: false + } else { + cm.activeNetworkInfo?.let { + it.isConnected && it.type == ConnectivityManager.TYPE_WIFI + } ?: false + } + } + + /** + * Determine device's IP address. + * + * Caveat: doesn't handle IPv6 addresses well. + */ + @JvmStatic + fun getLocalInetAddress(context: Context): InetAddress? { + if (!isConnectedToLocalNetwork(context)) { + return null + } + if (isConnectedToWifi(context)) { + val wm = context.applicationContext.getSystemService(Service.WIFI_SERVICE) + as WifiManager + val ipAddress = wm.connectionInfo.ipAddress + return if (ipAddress == 0) null else intToInet(ipAddress) + } + runCatching { + NetworkInterface.getNetworkInterfaces().iterator().forEach { netinterface -> + netinterface.inetAddresses.iterator().forEach { address -> + // this is the condition that sometimes gives problems + if (!address.isLoopbackAddress && + !address.isLinkLocalAddress + ) { + return address + } + } + } + }.onFailure { e -> + log.warn("failed to get local inet address", e) + } + return null + } + + /** + * Utility method to convert an IPv4 address in integer representation to [InetAddress]. + */ + @JvmStatic + fun intToInet(value: Int): InetAddress? { + val bytes = ByteArray(4) + for (i in 0..3) { + bytes[i] = byteOfInt(value, i) + } + return try { + InetAddress.getByAddress(bytes) + } catch (e: UnknownHostException) { + // This only happens if the byte array has a bad length + null + } + } + + private fun byteOfInt(value: Int, which: Int): Byte { + val shift = which * 8 + return (value shr shift).toByte() + } +} diff --git a/app/src/main/java/com/amaze/filemanager/utils/SubnetScanner.java b/app/src/main/java/com/amaze/filemanager/utils/SubnetScanner.java deleted file mode 100644 index 3f6abfbbf7..0000000000 --- a/app/src/main/java/com/amaze/filemanager/utils/SubnetScanner.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.utils; - -/** Created by arpitkh996 on 16-01-2016. */ -import static com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX; - -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import android.content.Context; -import android.net.wifi.WifiManager; -import android.os.AsyncTask; -import android.text.format.Formatter; - -import jcifs.Address; -import jcifs.CIFSException; -import jcifs.NetbiosAddress; -import jcifs.context.SingletonContext; -import jcifs.smb.SmbFile; - -public class SubnetScanner extends AsyncTask { - - private static final String TAG = SubnetScanner.class.getSimpleName(); - private static final int RETRY_COUNT = 5; - private static boolean initialized = false; - - private Thread bdThread; - private final Object mLock; - private List mResults; - private ScanObserver observer; - private ExecutorService pool; - private List> tasks; - private Context context; - - public interface ScanObserver { - void computerFound(ComputerParcelable computer); - - void searchFinished(); - } - - class Task implements Callable { - String addr; - - Task(String str) { - this.addr = str; - } - - public ComputerParcelable call() { - try { - NetbiosAddress[] allByAddress = - SingletonContext.getInstance().getNameServiceClient().getNbtAllByAddress(this.addr); - if (allByAddress == null || allByAddress.length <= 0) { - return new ComputerParcelable(null, this.addr); - } - return new ComputerParcelable(allByAddress[0].getHostName(), this.addr); - } catch (UnknownHostException e) { - return new ComputerParcelable(null, this.addr); - } - } - } - - public static void init() { - Properties props = new Properties(); - props.setProperty("jcifs.resolveOrder", "BCAST"); - props.setProperty("jcifs.smb.client.responseTimeout", "30000"); - props.setProperty("jcifs.netbios.retryTimeout", "5000"); - props.setProperty("jcifs.netbios.cachePolicy", "-1"); - try { - SingletonContext.init(props); - initialized = true; - } catch (CIFSException e) { - android.util.Log.e(TAG, "Error initializing jcifs", e); - } - } - - public SubnetScanner(Context context) { - this.context = context; - mLock = new Object(); - tasks = new ArrayList<>(260); - pool = Executors.newFixedThreadPool(60); - mResults = new ArrayList<>(); - } - - @Override - protected Void doInBackground(Void... voids) { - - if (!initialized) init(); - - int ipAddress = - ((WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE)) - .getConnectionInfo() - .getIpAddress(); - if (ipAddress != 0) { - tryWithBroadcast(); - String formatIpAddress = Formatter.formatIpAddress(ipAddress); - String substring = formatIpAddress.substring(0, formatIpAddress.lastIndexOf(46) + 1); - if (!isCancelled()) { - for (ipAddress = 0; ipAddress < 100; ipAddress++) { - this.tasks.add(this.pool.submit(new Task(substring + ipAddress))); - this.tasks.add(this.pool.submit(new Task(substring + (ipAddress + 100)))); - if (ipAddress < 56) { - this.tasks.add(this.pool.submit(new Task(substring + (ipAddress + 200)))); - } - } - while (!this.tasks.isEmpty()) { - int size = this.tasks.size(); - int i = 0; - while (i < size) { - if (!isCancelled()) { - try { - ComputerParcelable computer = - (ComputerParcelable) ((Future) this.tasks.get(i)).get(1, TimeUnit.MILLISECONDS); - this.tasks.remove(i); - size--; - if (computer.getName() != null) { - publishProgress(computer); - } - ipAddress = size; - } catch (InterruptedException e) { - return null; - } catch (ExecutionException e2) { - ipAddress = size; - } catch (TimeoutException e3) { - ipAddress = size; - } - i++; - size = ipAddress; - } else { - return null; - } - } - } - try { - this.bdThread.join(); - } catch (InterruptedException e4) { - } - } else { - return null; - } - } - synchronized (this.mLock) { - if (this.observer != null) { - this.observer.searchFinished(); - } - } - - return null; - } - - private void tryWithBroadcast() { - this.bdThread = - new Thread() { - public void run() { - for (int i = 0; i < SubnetScanner.RETRY_COUNT; i++) { - try { - SmbFile smbFile = SmbUtil.create(SMB_URI_PREFIX); - smbFile.setConnectTimeout(5000); - SmbFile[] listFiles = smbFile.listFiles(); - for (SmbFile smbFile2 : listFiles) { - SmbFile[] listFiles2 = smbFile2.listFiles(); - for (SmbFile files : listFiles2) { - try { - String substring = files.getName().substring(0, files.getName().length() - 1); - Address byName = - SingletonContext.getInstance() - .getNameServiceClient() - .getByName(substring); - if (byName != null) { - publishProgress(new ComputerParcelable(substring, byName.getHostAddress())); - } - } catch (Throwable e) { - - } - } - } - } catch (Throwable e2) { - - } - } - } - }; - this.bdThread.start(); - } - - @Override - protected void onPreExecute() {} - - @Override - protected void onPostExecute(Void aVoid) { - this.pool.shutdown(); - } - - @Override - protected void onProgressUpdate(ComputerParcelable... computers) { - for (ComputerParcelable computer : computers) { - mResults.add(computer); - synchronized (this.mLock) { - if (this.observer != null) { - this.observer.computerFound(computer); - } - } - } - } - - public void setObserver(ScanObserver scanObserver) { - synchronized (this.mLock) { - this.observer = scanObserver; - } - } - - @Override - protected void onCancelled(Void aVoid) { - super.onCancelled(aVoid); - try { - this.pool.shutdownNow(); - } catch (Throwable th) { - - } - } - - public List getResults() { - return new ArrayList<>(this.mResults); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/utils/UUIDv5.kt b/app/src/main/java/com/amaze/filemanager/utils/UUIDv5.kt new file mode 100644 index 0000000000..344482d034 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/utils/UUIDv5.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils + +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.* + +/** + * UUIDv5 implementation, referenced from + * https://gist.github.com/icedraco/00118b4d3c91d96d8c58e837a448f1b8 + */ +object UUIDv5 { + + // Constants defined in RFC4122 https://www.ietf.org/rfc/rfc4122.txt + @JvmStatic + val DNS: UUID = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + + @JvmStatic + val URL: UUID = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + + @JvmStatic + val OID: UUID = UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + + @JvmStatic + val X500: UUID = UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + + /** + * Generate an UUIDv5 UUID from given namespace UUID and name. + * + * [namespaceUUID] must be one of [DNS], [URL], [OID], [X500]. + */ + @JvmStatic + @Suppress("TooGenericExceptionThrown") + fun fromString(namespaceUUID: UUID, name: String): UUID { + val md: MessageDigest + try { + md = MessageDigest.getInstance("SHA-1") + } catch (ex: NoSuchAlgorithmException) { + throw Exception("SHA-1 not supported", ex) + } + + md.update(toBytes(namespaceUUID)) + md.update(name.toByteArray()) + val bytes = md.digest() + /* clear version; set to version 5 */ + bytes[6] = ((bytes[6].toInt() and 0x0F) or 0x50).toByte() + /* clear variant; set to IETF variant */ + bytes[8] = ((bytes[8].toInt() and 0x3F) or 0x80).toByte() + return fromBytes(bytes) + } + + private fun fromBytes(data: ByteArray): UUID { + // Based on the private UUID(bytes[]) constructor + assert(data.size >= 16) + var msb = 0L + var lsb = 0L + for (i in 0..7) + msb = msb shl 8 or (data[i].toLong() and 0xff) + for (i in 8..15) + lsb = lsb shl 8 or (data[i].toLong() and 0xff) + return UUID(msb, lsb) + } + + private fun toBytes(uuid: UUID): ByteArray { + // inverted logic of fromBytes() + val out = ByteArray(16) + val msb = uuid.mostSignificantBits + val lsb = uuid.leastSignificantBits + for (i in 0..7) + out[i] = (msb shr (7 - i) * 8 and 0xff).toByte() + for (i in 8..15) + out[i] = (lsb shr (15 - i) * 8 and 0xff).toByte() + return out + } +} diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt new file mode 100644 index 0000000000..277150c647 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils.smb + +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.utils.ComputerParcelable +import com.amaze.filemanager.utils.NetworkUtil +import com.stealthcopter.networktools.PortScan +import io.reactivex.Flowable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.net.Inet6Address +import java.net.InetAddress + +/** + * [SmbDeviceScannerObservable.DiscoverDeviceStrategy] to just loop through other addresses within + * same subnet (/24 netmask) and knock their SMB service ports for reachability. + * + * Will bypass [Inet6Address] device addresses. They may have much bigger neighourhood host count; + * also for devices using IPv6, they shall be covered by [WsddDiscoverDeviceStrategy] anyway. + * + * TODO: if we can get the gateway using __legit__ API, may swarm the network in broader netmasks + */ +class SameSubnetDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStrategy { + + private lateinit var worker: Disposable + + companion object { + private const val HOST_UP_TIMEOUT = 1000 + private const val PARALLELISM = 10 + private val TCP_PORTS = arrayListOf(139, 445) + } + + /** + * No need to cleanup resources + */ + override fun onCancel() { + if (!worker.isDisposed) { + worker.dispose() + } + } + + override fun discoverDevices(callback: (ComputerParcelable) -> Unit) { + val neighbourhoods = getNeighbourhoodHosts() + worker = Flowable.fromIterable(neighbourhoods) + .parallel(PARALLELISM) + .runOn(Schedulers.io()) + .map { addr -> + if (addr.isReachable(HOST_UP_TIMEOUT)) { + val portsReachable = listOf( + PortScan.onAddress(addr).setPorts(TCP_PORTS).setMethodTCP().doScan() + ).flatten() + if (portsReachable.isNotEmpty()) { + addr + } else { + false + } + } else { + false + } + }.filter { + it is InetAddress + }.doOnNext { addr -> + addr as InetAddress + callback.invoke(ComputerParcelable(addr.hostAddress, addr.hostName)) + }.sequential().subscribe() + } + + private fun getNeighbourhoodHosts(): List { + val deviceAddress = NetworkUtil.getLocalInetAddress(AppConfig.getInstance()) + return deviceAddress?.let { addr -> + if (addr is Inet6Address) { + // IPv6 neigbourhood hosts can be very big - that should use wsdd instead; hence + // empty list here + emptyList() + } else { + val networkPrefix: String = addr.hostAddress.substringBeforeLast('.') + (1..254).map { + InetAddress.getByName("$networkPrefix.$it") + } + } + } ?: emptyList() + } +} diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt new file mode 100644 index 0000000000..14dcced9d8 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils.smb + +import com.amaze.filemanager.utils.ComputerParcelable +import com.amaze.filemanager.utils.smb.SmbDeviceScannerObservable.DiscoverDeviceStrategy +import io.reactivex.Observable +import io.reactivex.Observer +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import java.net.InetAddress + +/** + * Observable to discover reachable SMB nodes on the network. + * + * Uses a series of [DiscoverDeviceStrategy] instances to discover nodes. + */ +class SmbDeviceScannerObservable : Observable() { + + /** + * Device discovery strategy interface. + */ + interface DiscoverDeviceStrategy { + /** + * Implement this method to return list of [InetAddress] which has SMB service running. + */ + fun discoverDevices(callback: (ComputerParcelable) -> Unit) + + /** + * Implement this method to cleanup resources + */ + fun onCancel() + } + + private var discoverDeviceStrategies: Array = + arrayOf( + WsddDiscoverDeviceStrategy(), + SameSubnetDiscoverDeviceStrategy() + ) + + private lateinit var observer: Observer + + private lateinit var disposable: Disposable + + /** + * Stop discovering hosts. Notify containing strategies to stop, then stop the created + * [Observer] obtained at [subscribeActual]. + */ + fun stop() { + if (!disposable.isDisposed) { + disposable.dispose() + } + observer.onComplete() + } + + /** + * Call all strategies one by one to discover nodes. + * + * Given observer must be able to drop duplicated entries (which ComputerParcelable already + * has implemented equals() and hashCode()). + */ + override fun subscribeActual(observer: Observer) { + this.observer = observer + this.disposable = merge( + discoverDeviceStrategies.map { strategy -> + fromCallable { + strategy.discoverDevices { addr -> + observer.onNext(ComputerParcelable(addr.addr, addr.name)) + } + }.subscribeOn(Schedulers.io()) + } + ).observeOn(Schedulers.computation()).doOnComplete { + discoverDeviceStrategies.forEach { strategy -> + strategy.onCancel() + } + }.subscribe() + } +} diff --git a/app/src/main/java/com/amaze/filemanager/utils/SmbUtil.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt similarity index 98% rename from app/src/main/java/com/amaze/filemanager/utils/SmbUtil.kt rename to app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt index 35ae7bd5b9..eca1603f92 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/SmbUtil.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -package com.amaze.filemanager.utils +package com.amaze.filemanager.utils.smb import android.content.Context import android.net.Uri @@ -30,6 +30,8 @@ import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.AT import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.COLON import com.amaze.filemanager.filesystem.smb.CifsContexts.createWithDisableIpcSigningCheck +import com.amaze.filemanager.utils.PasswordUtil +import com.amaze.filemanager.utils.urlDecoded import io.reactivex.Single import io.reactivex.schedulers.Schedulers import jcifs.smb.NtlmPasswordAuthenticator diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt new file mode 100644 index 0000000000..33e1813c69 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils.smb + +import androidx.annotation.VisibleForTesting +import com.amaze.filemanager.R +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.utils.ComputerParcelable +import com.amaze.filemanager.utils.NetworkUtil +import com.android.volley.Response.ErrorListener +import com.android.volley.VolleyError +import com.android.volley.toolbox.StringRequest +import com.android.volley.toolbox.Volley +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory +import java.io.StringReader +import java.net.DatagramPacket +import java.net.InetAddress +import java.net.MulticastSocket +import java.util.* + +/** + * [SmbDeviceScannerObservable.DiscoverDeviceStrategy] implementation to discover SMB devices using + * [Web service discovery](https://en.wikipedia.org/wiki/WS-Discovery), which is used by SMBv2 or + * above. + * + * Discovery method goes this way: + * 1. send a SOAP request to multicast address 239.255.255.250 port 3702 over UDP + * 2. for each reply as SOAP XML too, extract their URN and record the address the packets are from + * 3. if the reply indicates sender is a computer, send a HTTP POST to the address recorded in 2, port 5357 + * 4. verify result and send [ComputerParcelable] in callback + * + * Implementation is after reference: https://fitzcarraldoblog.wordpress.com/2020/07/08/a-linux-command-line-utility-to-discover-and-list-wsd-enabled-computers-and-printers-on-a-home-network/ + * (Python though). + * + * Original implementation calls for UUIDv5 which will use hash value of the device's MAC address; + * this implementation is not using, since MAC address poses privacy concern, and newer Androids are + * making difficult to fetch MAC addresses anyway. + * + * Manually setting [multicastSocketFactory] Allows customized method to be specified for creating [MulticastSocket] + * for convenience of testing. + * + * @author TranceLove + */ +class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStrategy { + + private val multicastRequestTemplate = AppConfig.getInstance() + .resources.openRawResource(R.raw.wsdd_discovery) + .reader(Charsets.UTF_8).readText() + + private val wsdRequestTemplate = AppConfig.getInstance() + .resources.openRawResource(R.raw.wsd_request) + .reader(Charsets.UTF_8).readText() + + private val wsdRequestHeaders = mutableMapOf( + Pair("Accept-Encoding", "Identity"), + Pair("Content-Type", "application/soap+xml"), + Pair("Connection", "Close"), + Pair("User-Agent", "wsd") + ) + + var multicastSocketFactory: () -> MulticastSocket = DEFAULT_MULTICAST_SOCKET_FACTORY + @VisibleForTesting + get + + @VisibleForTesting + set + + private val queue = Volley.newRequestQueue(AppConfig.getInstance()) + + private var cancelled = false + + init { + queue.start() + } + + override fun discoverDevices(callback: (ComputerParcelable) -> Unit) { + multicastForDevice { addr -> + callback.invoke(addr) + } + } + + @Suppress("LabeledExpression") + private fun multicastForDevice(callback: (ComputerParcelable) -> Unit) { + NetworkUtil.getLocalInetAddress(AppConfig.getInstance())?.let { addr -> + val multicastAddressV4 = InetAddress.getByName(BROADCAST_IPV4) + val multicastAddressV6 = InetAddress.getByName(BROADCAST_IPV6_LINK_LOCAL) + + while (!cancelled) { + val socket: MulticastSocket = multicastSocketFactory.invoke() + socket.timeToLive = 1 + socket.soTimeout = SOCKET_RECEIVE_TIMEOUT + socket.reuseAddress = true + socket.joinGroup(multicastAddressV4) + socket.joinGroup(multicastAddressV6) + + // Specification said UUIDv5 which is device dependent. But random-based UUID should + // also work here + val tempDeviceUuid = UUID.randomUUID() + val request = multicastRequestTemplate + .replace("##MY_UUID##", tempDeviceUuid.toString()) + .toByteArray(Charsets.UTF_8) + + val requestPacket = DatagramPacket( + request, + request.size, + multicastAddressV4, + UDP_PORT + ) + socket.send(requestPacket) + + runCatching { + while (!socket.isClosed) { + val buffer = ByteArray(4096) + val replyPacket = DatagramPacket(buffer, buffer.size) + socket.receive(replyPacket) + if (replyPacket.data.isNotEmpty() && replyPacket.address != null) { + val sentFromAddress = replyPacket.address + queryWithResponseAsNecessary( + sentFromAddress, + tempDeviceUuid.toString(), + replyPacket.data, + callback + ) + } + } + }.onFailure { + if (log.isWarnEnabled) log.warn("Error receiving reply", it) + socket.close() + } + } + } + } + + private fun queryWithResponseAsNecessary( + sourceAddress: InetAddress, + tempDeviceId: String, + response: ByteArray, + callback: (ComputerParcelable) -> Unit + ) { + val values = parseXmlForResponse(response, arrayOf(WSD_TYPES, WSA_ADDRESS)) + val type = values[WSD_TYPES] + val urn = values[WSA_ADDRESS] + + if (true == type?.isNotEmpty() && true == urn?.isNotEmpty()) { + queryEndpointForResponse(type, sourceAddress, urn, tempDeviceId, callback) + } + } + + private fun queryEndpointForResponse( + type: String, + sourceAddress: InetAddress, + urn: String, + tempDeviceId: String, + callback: (ComputerParcelable) -> Unit + ) { + if (type.endsWith(PUB_COMPUTER)) { + val messageId = UUID.randomUUID().toString() + + val endpoint = urn.substringAfter(URN_UUID) + val dest = + "http://${sourceAddress.hostAddress}:$TCP_PORT/$endpoint" + queue.add( + object : StringRequest( + Method.POST, + dest, + { resp -> + if (log.isTraceEnabled) log.trace("Response: $resp") + val values = parseXmlForResponse( + resp, + arrayOf(WSDP_TYPES, WSA_ADDRESS, PUB_COMPUTER) + ) + if (PUB_COMPUTER == values[WSDP_TYPES] && urn == values[WSA_ADDRESS]) { + if (true == values[PUB_COMPUTER]?.isNotEmpty()) { + val computerName: String = values[PUB_COMPUTER].let { + if (it!!.contains('/')) { + it.substringBefore("/") + } else { + it + } + } + callback( + ComputerParcelable(sourceAddress.hostAddress, computerName) + ) + } + } + }, + object : ErrorListener { + override fun onErrorResponse(error: VolleyError?) { + log.error("Error querying endpoint", error) + } + } + ) { + override fun getBody(): ByteArray { + return wsdRequestTemplate + .replace("##MESSAGE_ID##", "$URN_UUID$messageId") + .replace("##DEST_UUID##", urn) + .replace("##MY_UUID##", "$URN_UUID$tempDeviceId") + .toByteArray(Charsets.UTF_8) + } + override fun getHeaders(): MutableMap = wsdRequestHeaders + } + ) + } + } + + override fun onCancel() { + cancelled = true + queue.stop() + } + + private fun parseXmlForResponse(xml: ByteArray, tags: Array) = + parseXmlForResponse(xml.toString(Charsets.UTF_8), tags) + + private fun parseXmlForResponse(xml: String, tags: Array): Map { + if (xml.isEmpty()) { + return emptyMap() + } else { + val xmlParser = XmlPullParserFactory.newInstance().also { + it.isNamespaceAware = false + it.isValidating = false + }.newPullParser().also { + it.setInput(StringReader(xml)) + } + val retval = WeakHashMap() + var currentTag: String = "" + var currentValue: String = "" + var event = xmlParser.eventType + try { + while (event != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG) { + currentTag = xmlParser.name + } else if (event == XmlPullParser.TEXT) { + currentValue = xmlParser.text + } else if (event == XmlPullParser.END_TAG) { + if (tags.contains(currentTag)) { + retval[currentTag] = currentValue + currentTag = "" + currentValue = "" + } + } + event = xmlParser.next() + } + } catch (parseError: XmlPullParserException) { + log.warn("Error parsing XML", parseError) + // Combination of parsed result is required, hence it's all or nothing situation - + // if one error found, whole XML will not be valid. Clear for "no result" answer + retval.clear() + } + return retval + } + } + + companion object { + private const val BROADCAST_IPV4 = "239.255.255.250" + private const val BROADCAST_IPV6_LINK_LOCAL = "[FF02::C]" + private const val UDP_PORT = 3702 + private const val TCP_PORT = 5357 + private const val SOCKET_RECEIVE_TIMEOUT = 60000 // 1 minute receive timeout + + private const val URN_UUID = "urn:uuid:" + private const val WSA_ADDRESS = "wsa:Address" + private const val WSD_TYPES = "wsd:Types" + private const val WSDP_TYPES = "wsdp:Types" + private const val PUB_COMPUTER = "pub:Computer" + + private val log: Logger = LoggerFactory.getLogger(WsddDiscoverDeviceStrategy::class.java) + + private val DEFAULT_MULTICAST_SOCKET_FACTORY: () -> MulticastSocket = { + MulticastSocket() + } + } +} diff --git a/app/src/main/res/raw/wsd_request.txt b/app/src/main/res/raw/wsd_request.txt new file mode 100644 index 0000000000..e7ae2d6f85 --- /dev/null +++ b/app/src/main/res/raw/wsd_request.txt @@ -0,0 +1,22 @@ + + + + ##DEST_UUID## + http://schemas.xmlsoap.org/ws/2004/09/transfer/Get + ##MESSAGE_ID## + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + + ##MY_UUID## + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/wsdd_discovery.txt b/app/src/main/res/raw/wsdd_discovery.txt new file mode 100644 index 0000000000..aa39fe0676 --- /dev/null +++ b/app/src/main/res/raw/wsdd_discovery.txt @@ -0,0 +1 @@ +urn:schemas-xmlsoap-org:ws:2005:04:discoveryhttp://schemas.xmlsoap.org/ws/2005/04/discovery/Probeurn:uuid:##MY_UUID##wsdp:Device \ No newline at end of file diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/SmbDeleteTaskTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/SmbDeleteTaskTest.kt index 9bd9b32b37..d440d38285 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/SmbDeleteTaskTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/SmbDeleteTaskTest.kt @@ -22,7 +22,7 @@ package com.amaze.filemanager.asynchronous.asynctasks import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.shadows.ShadowSmbUtil.Companion.PATH_CANNOT_DELETE_FILE -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import org.junit.Test class SmbDeleteTaskTest : AbstractDeleteTaskTestBase() { diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt index 85fc4c54a0..e790bfca0f 100644 --- a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt +++ b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt @@ -39,7 +39,7 @@ import com.amaze.filemanager.database.UtilitiesDatabase.Companion.TABLE_SMB import com.amaze.filemanager.shadows.ShadowMultiDex import com.amaze.filemanager.test.ShadowPasswordUtil import com.amaze.filemanager.utils.PasswordUtil -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt index 9a208729fa..842c7506f8 100644 --- a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt +++ b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt @@ -35,7 +35,7 @@ import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.database.models.OperationData import com.amaze.filemanager.shadows.ShadowMultiDex import com.amaze.filemanager.test.ShadowPasswordUtil -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import io.reactivex.android.plugins.RxAndroidPlugins import io.reactivex.plugins.RxJavaPlugins import io.reactivex.schedulers.Schedulers diff --git a/app/src/test/java/com/amaze/filemanager/utils/ComputerParcelableTest.java b/app/src/test/java/com/amaze/filemanager/utils/ComputerParcelableTest.java index 1624c290a0..07a1abb70b 100644 --- a/app/src/test/java/com/amaze/filemanager/utils/ComputerParcelableTest.java +++ b/app/src/test/java/com/amaze/filemanager/utils/ComputerParcelableTest.java @@ -54,15 +54,15 @@ public void testObjectNotEqualsName() { /** * Purpose: when computerParcelable's address and object's address are not the same, confirm that - * the two are different. Input: computerParcelable.equals(object) only ComputerParcelable.name == - * Object.name Expected: result is false + * the two are different. Input: computerParcelable.equals(object) only ComputerParcelable.addr == + * Object.name Expected: result is true */ @Test public void testObjectNotEqualsAddr() { ComputerParcelable computerParcelable = new ComputerParcelable("com1", "1"); Object object = new ComputerParcelable("com1", "2"); - assertFalse(computerParcelable.equals(object)); + assertTrue(computerParcelable.equals(object)); } /** diff --git a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt index 697fde2f18..352eec1d06 100644 --- a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt +++ b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt @@ -29,10 +29,10 @@ import com.amaze.filemanager.fileoperations.filesystem.DOESNT_EXIST import com.amaze.filemanager.fileoperations.filesystem.WRITABLE_ON_REMOTE import com.amaze.filemanager.shadows.ShadowSmbUtil import com.amaze.filemanager.test.ShadowPasswordUtil -import com.amaze.filemanager.utils.SmbUtil.checkFolder -import com.amaze.filemanager.utils.SmbUtil.createFrom -import com.amaze.filemanager.utils.SmbUtil.getSmbDecryptedPath -import com.amaze.filemanager.utils.SmbUtil.getSmbEncryptedPath +import com.amaze.filemanager.utils.smb.SmbUtil.checkFolder +import com.amaze.filemanager.utils.smb.SmbUtil.createFrom +import com.amaze.filemanager.utils.smb.SmbUtil.getSmbDecryptedPath +import com.amaze.filemanager.utils.smb.SmbUtil.getSmbEncryptedPath import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue diff --git a/app/src/test/java/com/amaze/filemanager/utils/UUIDv5Test.kt b/app/src/test/java/com/amaze/filemanager/utils/UUIDv5Test.kt new file mode 100644 index 0000000000..6f4261978f --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/utils/UUIDv5Test.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Test for [UUIDv5]. + */ +class UUIDv5Test { + + /** + * Test UUID generation. Value is based on SHA-1 hash, so it can be expected. + * + * Test case taken (again) from + * https://gist.github.com/icedraco/00118b4d3c91d96d8c58e837a448f1b8 + */ + @Test + fun testGenerateUUID() { + val url = "http://www.whatever.com/test/" + val uuid = UUIDv5.fromString(UUIDv5.URL, url) + assertEquals("1730930d-a36a-5efd-aa3f-561a164f87a4", uuid.toString()) + } +} diff --git a/app/src/test/java/com/amaze/filemanager/utils/smb/AbstractSubnetDiscoverDevicesStrategyTests.kt b/app/src/test/java/com/amaze/filemanager/utils/smb/AbstractSubnetDiscoverDevicesStrategyTests.kt new file mode 100644 index 0000000000..3b44ccdc1e --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/utils/smb/AbstractSubnetDiscoverDevicesStrategyTests.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils.smb + +import android.os.Build.VERSION_CODES +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.utils.NetworkUtil +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.After +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import java.net.InetAddress + +/** + * Base class for [SmbDeviceScannerObservable.DiscoverDeviceStrategy] tests. + */ +@RunWith(AndroidJUnit4::class) +@Config(sdk = [KITKAT, P, VERSION_CODES.R]) +abstract class AbstractSubnetDiscoverDevicesStrategyTests { + + /** + * Post test cleanup. + */ + @After + open fun tearDown() { + unmockkStatic(NetworkUtil::class) + } + + protected fun deviceOffline() { + mockkStatic(NetworkUtil::class) + every { NetworkUtil.isConnectedToWifi(any()) } returns false + every { NetworkUtil.isConnectedToLocalNetwork(any()) } returns false + every { NetworkUtil.getLocalInetAddress(any()) } returns null + } + + protected fun deviceOnline() { + mockkStatic(NetworkUtil::class) + every { NetworkUtil.isConnectedToWifi(any()) } returns true + every { NetworkUtil.isConnectedToLocalNetwork(any()) } returns true + every { NetworkUtil.getLocalInetAddress(any()) } returns mockk().also { + every { it.hostName } returns "192.168.233.240" + } + } + + protected fun mockInetAddress(hostName: String, hostAddress: String): InetAddress { + val upHost = mockk() + every { upHost.hostName } returns hostName + every { upHost.hostAddress } returns hostAddress + every { InetAddress.getByName(hostAddress) } returns upHost + return upHost + } +} diff --git a/app/src/test/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDevicesStrategyTest.kt b/app/src/test/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDevicesStrategyTest.kt new file mode 100644 index 0000000000..1cd428f1f6 --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDevicesStrategyTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils.smb + +import com.amaze.filemanager.utils.ComputerParcelable +import org.junit.Assert.assertEquals +import org.junit.Test +import org.slf4j.LoggerFactory +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class SameSubnetDiscoverDevicesStrategyTest : AbstractSubnetDiscoverDevicesStrategyTests() { + + companion object { + @JvmStatic + private val logger = LoggerFactory.getLogger( + SameSubnetDiscoverDevicesStrategyTest::class.java + ) + } + + /** + * Test if device is not connected to network. + */ + @Test + fun testDiscoverIfNotConnected() { + deviceOffline() + val latch = CountDownLatch(1) + val result = ArrayList() + SameSubnetDiscoverDeviceStrategy().discoverDevices { + result.add(it) + latch.countDown() + } + try { + latch.await(1, TimeUnit.SECONDS) + } catch (_: Throwable) { + latch.countDown() + } + assertEquals(0, result.size) + } +} diff --git a/app/src/test/java/com/amaze/filemanager/utils/smb/WsddSubnetDiscoverDevicesStrategyTest.kt b/app/src/test/java/com/amaze/filemanager/utils/smb/WsddSubnetDiscoverDevicesStrategyTest.kt new file mode 100644 index 0000000000..0212756d3b --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/utils/smb/WsddSubnetDiscoverDevicesStrategyTest.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra , + * Emmanuel Messulam, Raymond Lai and Contributors. + * + * This file is part of Amaze File Manager. + * + * Amaze File Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.amaze.filemanager.utils.smb + +import com.amaze.filemanager.test.randomBytes +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.robolectric.util.ReflectionHelpers +import java.util.* +import kotlin.text.Charsets.UTF_8 + +/** + * Test [WsddDiscoverDeviceStrategy]. + */ +@Suppress("StringLiteralDuplication") +class WsddSubnetDiscoverDevicesStrategyTest : AbstractSubnetDiscoverDevicesStrategyTests() { + + private val multicastResponseTemplate = javaClass.classLoader!! + .getResourceAsStream("wsdd/multicast-response.txt") + .reader(UTF_8).readText() + + private val wsdResponseTemplate = javaClass.classLoader!! + .getResourceAsStream("wsdd/wsd-response.txt") + .reader(UTF_8).readText() + + private val parseXmlForResponse: + (WsddDiscoverDeviceStrategy, Any, Array) -> Map = + { instance, xml, tags -> + require((xml is ByteArray) or (xml is String)) + ReflectionHelpers.callInstanceMethod( + WsddDiscoverDeviceStrategy::class.java, + instance, + "parseXmlForResponse", + ReflectionHelpers.ClassParameter(xml.javaClass, xml), + ReflectionHelpers.ClassParameter(Array::class.java, tags) + ) + } + + private lateinit var wsdMulticastResponseMessageId: String + private lateinit var deviceId: String + + /** + * Test for normal parsing of multicast response + */ + @Test + fun testParseMulticastResponse() { + val instance = WsddDiscoverDeviceStrategy() + val result = parseXmlForResponse.invoke( + instance, + createMulticastResponse(), + arrayOf("wsd:Types", "wsa:Address") + ) + assertTrue(result.isNotEmpty()) + assertTrue(result.containsKey("wsd:Types")) + assertTrue(result.containsKey("wsa:Address")) + assertTrue(true == result["wsd:Types"]?.isNotBlank()) + assertTrue(true == result["wsa:Address"]?.isNotBlank()) + } + + /** + * Test parsing invalid XML and/or invalid/nonexistent tags in XML. + */ + @Test + fun testParseInvalidMulticastResponse() { + val instance = WsddDiscoverDeviceStrategy() + assertTrue(parseXmlForResponse.invoke(instance, "", emptyArray()).isEmpty()) + assertTrue(parseXmlForResponse.invoke(instance, "foobar", emptyArray()).isEmpty()) + assertTrue(parseXmlForResponse.invoke(instance, "", emptyArray()).isEmpty()) + assertTrue( + parseXmlForResponse.invoke( + instance, + ByteArray(0), + emptyArray() + ).isEmpty() + ) + assertTrue( + parseXmlForResponse.invoke( + instance, + "foobar".toByteArray(), + emptyArray() + ).isEmpty() + ) + assertTrue( + parseXmlForResponse.invoke( + instance, + randomBytes(), + emptyArray() + ).isEmpty() + ) + } + + /** + * Test parsing of valid XML but with non-matching tags in XML. + */ + @Test + fun testParseNonMatchingMulticastResponseParams() { + val instance = WsddDiscoverDeviceStrategy() + assertEquals( + 0, + parseXmlForResponse.invoke( + instance, + "", + arrayOf("foobar") + ).size + ) + assertEquals( + 0, + parseXmlForResponse.invoke( + instance, + "", + arrayOf("test") + ).size + ) + } + + private fun createMulticastResponse(): String { + return multicastResponseTemplate.replace( + "##DEVICE_UUID##", + UUID.randomUUID().toString() + ).replace( + "##MESSAGE_ID##", + UUID.randomUUID().toString() + ).replace( + "##SRC_MESSAGE_ID##", + UUID.randomUUID().toString() + ) + } + + private fun generateWsdResponse(deviceName: String, workgroupName: String = "WORKGROUP") = + wsdResponseTemplate + .replace("##THIS_DEVICE_ID##", deviceId) + .replace("##DEVICE_NAME##", deviceName) + .replace("##WORKGROUP_NAME##", workgroupName) + .replace("##PREV_MESSAGE_ID##", wsdMulticastResponseMessageId) + .replace("##THIS_MESSAGE_ID##", UUID.randomUUID().toString()) + .toByteArray(UTF_8) +} diff --git a/app/src/test/resources/wsdd/multicast-response.txt b/app/src/test/resources/wsdd/multicast-response.txt new file mode 100644 index 0000000000..5a8af6aa9c --- /dev/null +++ b/app/src/test/resources/wsdd/multicast-response.txt @@ -0,0 +1,28 @@ + + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches + urn:uuid:##MESSAGE_ID## + urn:uuid:##SRC_MESSAGE_ID## + + + + + + + urn:uuid:##DEVICE_UUID## + + wsdp:Device pub:Computer + 1 + + + + \ No newline at end of file diff --git a/app/src/test/resources/wsdd/wsd-response.txt b/app/src/test/resources/wsdd/wsd-response.txt new file mode 100644 index 0000000000..7eb119a43f --- /dev/null +++ b/app/src/test/resources/wsdd/wsd-response.txt @@ -0,0 +1,46 @@ + + + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse + urn:uuid:##THIS_MESSAGE_ID## + urn:uuid:##PREV_MESSAGE_ID## + + + + + + WSD Device ##DEVICE_NAME## + 1.0 + 1 + + + + + wsdd + wsdd + Computers + + + + + + + urn:uuid:##THIS_DEVICE_ID## + + pub:Computer + urn:uuid:##THIS_DEVICE_ID## + ##DEVICE_NAME##/Workgroup:##WORKGROUP_NAME## + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7197d6553f..3fd0ef3566 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,8 @@ buildscript { commonsNetVersion = "3.8.0" ftpserverVersion = "1.1.1" jsoupVersion = "1.13.1" + rxAndroidVersion = "2.1.1" + rxJavaVersion = "2.2.9" } repositories { google() @@ -76,7 +78,7 @@ allprojects { spotless { java { licenseHeaderFile 'spotless.license-java' - target 'app/src/**/*.java', 'commons_compress_7z/src/**/*.java', 'file_operations/src/**/*.java' + target 'app/src/**/*.java', 'commons_compress_7z/src/**/*.java', 'file_operations/src/**/*.java', 'portscanner/src/**/*.java' googleJavaFormat('1.15.0') removeUnusedImports() // removes any unused imports importOrder 'java', 'javax', 'org', 'com', 'android', 'androidx', '' diff --git a/portscanner/.gitignore b/portscanner/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/portscanner/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/portscanner/build.gradle b/portscanner/build.gradle new file mode 100644 index 0000000000..355d17f1c3 --- /dev/null +++ b/portscanner/build.gradle @@ -0,0 +1,37 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' + +android { + namespace 'com.stealthcopter.networktools' + compileSdk 32 + + defaultConfig { + minSdk 14 + targetSdk 32 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" + // Because RxAndroid releases are few and far between, it is recommended you also + // explicitly depend on RxJava's latest version for bug fixes and new features. + // (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version) + implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion" + testImplementation 'junit:junit:4.13.2' +} \ No newline at end of file diff --git a/portscanner/consumer-rules.pro b/portscanner/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/portscanner/proguard-rules.pro b/portscanner/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/portscanner/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/portscanner/src/main/AndroidManifest.xml b/portscanner/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..100ab263c3 --- /dev/null +++ b/portscanner/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/portscanner/src/main/java/com/stealthcopter/networktools/IPTools.kt b/portscanner/src/main/java/com/stealthcopter/networktools/IPTools.kt new file mode 100644 index 0000000000..59fac0768e --- /dev/null +++ b/portscanner/src/main/java/com/stealthcopter/networktools/IPTools.kt @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.stealthcopter.networktools + +import java.net.Inet4Address +import java.net.InetAddress +import java.net.NetworkInterface +import java.net.SocketException +import java.util.regex.Pattern + +/** + * Created by mat on 14/12/15. + */ +object IPTools { + /* + * Ip matching patterns from + * https://examples.javacodegeeks.com/core-java/util/regex/regular-expressions-for-ip-v4-and-ip-v6-addresses/ + * note that these patterns will match most but not all valid ips + */ + private val IPV4_PATTERN = Pattern.compile( + "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$" + ) + private val IPV6_STD_PATTERN = Pattern.compile( + "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$" + ) + private val IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile( + "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$" + ) + + /** + * Answers if given string is valid IPv4 address. + */ + @JvmStatic + fun isIPv4Address(address: String?): Boolean { + return address != null && IPV4_PATTERN.matcher(address).matches() + } + + /** + * Answers if given string is valid IPv6 address in long form. + */ + @JvmStatic + fun isIPv6StdAddress(address: String?): Boolean { + return address != null && IPV6_STD_PATTERN.matcher(address).matches() + } + + /** + * Answers if given string is valid IPv6 address in hex compressed form. + */ + @JvmStatic + fun isIPv6HexCompressedAddress(address: String?): Boolean { + return address != null && IPV6_HEX_COMPRESSED_PATTERN.matcher(address).matches() + } + + /** + * Answers if given string is a valid IPv6 address. + */ + @JvmStatic + fun isIPv6Address(address: String?): Boolean { + return address != null && (isIPv6StdAddress(address) || isIPv6HexCompressedAddress(address)) + } + + /* + * @return The first local IPv4 address, or null + */ + @JvmStatic + val localIPv4Address: InetAddress? + get() { + val localAddresses = localIPv4Addresses + return if (localAddresses.isNotEmpty()) localAddresses[0] else null + } + + /* + * Return The list of all IPv4 addresses found + */ + private val localIPv4Addresses: List + get() = runCatching { + NetworkInterface.getNetworkInterfaces().toList().flatMap { iface -> + iface.inetAddresses.asSequence().filter { addr -> + addr is Inet4Address && !addr.isLoopbackAddress() + } + } + }.getOrDefault(emptyList()) + + /** + * Check if the provided ip address refers to the localhost + * + * https://stackoverflow.com/a/2406819/315998 + * + * @param addr - address to check + * @return - true if ip address is self + */ + @JvmStatic + fun isIpAddressLocalhost(addr: InetAddress?): Boolean { + return addr?.run { + // Check if the address is a valid special local or loop back + if (addr.isAnyLocalAddress || addr.isLoopbackAddress) true else try { + NetworkInterface.getByInetAddress(addr) != null + } catch (e: SocketException) { + false + } + } ?: false + } + + /** + * Check if the provided ip address refers to the localhost + * + * https://stackoverflow.com/a/2406819/315998 + * + * @param addr - address to check + * @return - true if ip address is self + */ + @JvmStatic + fun isIpAddressLocalNetwork(addr: InetAddress?): Boolean = + addr != null && addr.isSiteLocalAddress +} diff --git a/portscanner/src/main/java/com/stealthcopter/networktools/PortScan.kt b/portscanner/src/main/java/com/stealthcopter/networktools/PortScan.kt new file mode 100644 index 0000000000..0f9f9812ef --- /dev/null +++ b/portscanner/src/main/java/com/stealthcopter/networktools/PortScan.kt @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.stealthcopter.networktools + +import com.stealthcopter.networktools.IPTools.isIpAddressLocalNetwork +import com.stealthcopter.networktools.IPTools.isIpAddressLocalhost +import com.stealthcopter.networktools.portscanning.PortScanTCP +import com.stealthcopter.networktools.portscanning.PortScanUDP +import io.reactivex.Flowable +import io.reactivex.schedulers.Schedulers +import java.net.InetAddress +import java.net.UnknownHostException +import java.util.* + +/** Created by mat on 14/12/15. */ +class PortScan // This class is not to be instantiated +private constructor() { + + private var method = METHOD_TCP + private var noThreads = 50 + private var address: InetAddress? = null + private var timeOutMillis = 1000 + private var cancelled = false + private var ports: MutableList = ArrayList() + private val openPortsFound: MutableList = ArrayList() + private var portListener: PortListener? = null + private lateinit var runningFlowable: Flowable + + interface PortListener { + /** + * Callback function for port scan result + */ + fun onResult(portNo: Int, open: Boolean) + + /** + * Callback function for receiving the list of opened ports + */ + fun onFinished(openPorts: List?) + } + + /** + * Sets the timeout for each port scanned + * + * If you raise the timeout you may want to consider increasing the thread count [ ][.setNoThreads] to compensate. We can afford to have quite a high thread count as most of + * the time the thread is just sitting idle and waiting for the socket to timeout. + * + * @param timeOutMillis - the timeout for each ping in milliseconds Recommendations: Local host: + * 20 - 500 ms - can be very fast as request doesn't need to go over network Local network 500 + * - 2500 ms Remote Scan 2500+ ms + * @return this object to allow chaining + */ + fun setTimeOutMillis(timeOutMillis: Int): PortScan { + require(timeOutMillis >= 0) { "Timeout cannot be less than 0" } + this.timeOutMillis = timeOutMillis + return this + } + + /** + * Scan the ports to scan + * + * @param port - the port to scan + * @return this object to allow chaining + */ + fun setPort(port: Int): PortScan { + ports.clear() + validatePort(port) + ports.add(port) + return this + } + + /** + * Scan the ports to scan + * + * @param ports - the ports to scan + * @return this object to allow chaining + */ + fun setPorts(ports: MutableList): PortScan { + // Check all ports are valid + for (port in ports) { + validatePort(port) + } + this.ports = ports + return this + } + + /** + * Scan the ports to scan + * + * @param portString - the ports to scan (comma separated, hyphen denotes a range). For example: + * "21-23,25,45,53,80" + * @return this object to allow chaining + */ + fun setPorts(portString: String): PortScan { + var portString = portString + ports.clear() + val ports: MutableList = ArrayList() + portString = portString.substring(portString.indexOf(":") + 1, portString.length) + for (x in portString.split(",").toTypedArray()) { + if (x.contains("-")) { + val start = x.split("-").toTypedArray()[0].toInt() + val end = x.split("-").toTypedArray()[1].toInt() + validatePort(start) + validatePort(end) + require(end > start) { "Start port cannot be greater than or equal to the end port" } + for (j in start..end) { + ports.add(j) + } + } else { + val start = x.toInt() + validatePort(start) + ports.add(start) + } + } + this.ports = ports + return this + } + + /** + * Checks and throws exception if port is not valid + * + * @param port - the port to validate + */ + private fun validatePort(port: Int) { + require(port >= 1) { "Start port cannot be less than 1" } + require(port <= 65535) { "Start cannot be greater than 65535" } + } + + private fun setAddress(address: InetAddress) { + this.address = address + } + + private fun setDefaultThreadsAndTimeouts() { + // Try and work out automatically what kind of host we are scanning + // local host (this device) / local network / remote + if (isIpAddressLocalhost(address)) { + // If we are scanning a the localhost set the timeout to be very short so we get faster + // results + // This will be overridden if user calls setTimeoutMillis manually. + timeOutMillis = TIMEOUT_LOCALHOST + noThreads = DEFAULT_THREADS_LOCALHOST + } else if (isIpAddressLocalNetwork(address)) { + // Assume local network (not infallible) + timeOutMillis = TIMEOUT_LOCALNETWORK + noThreads = DEFAULT_THREADS_LOCALNETWORK + } else { + // Assume remote network timeouts + timeOutMillis = TIMEOUT_REMOTE + noThreads = DEFAULT_THREADS_REMOTE + } + } + + /** + * @param noThreads set the number of threads to work with, note we default to a large number as + * these requests are network heavy not cpu heavy. + * @return self + * @throws IllegalArgumentException - if no threads is less than 1 + */ + @Throws(IllegalArgumentException::class) + fun setNoThreads(noThreads: Int): PortScan { + require(noThreads >= 1) { "Cannot have less than 1 thread" } + this.noThreads = noThreads + return this + } + + /** + * Set scan method, either TCP or UDP + * + * @param method - the transport method to use to scan, either PortScan.METHOD_UDP or + * PortScan.METHOD_TCP + * @return this object to allow chaining + * @throws IllegalArgumentException - if invalid method + */ + private fun setMethod(method: Int): PortScan { + when (method) { + METHOD_UDP, METHOD_TCP -> this.method = method + else -> throw IllegalArgumentException("Invalid method type $method") + } + return this + } + + /** + * Set scan method to UDP + * + * @return this object to allow chaining + */ + fun setMethodUDP(): PortScan { + setMethod(METHOD_UDP) + return this + } + + /** + * Set scan method to TCP + * + * @return this object to allow chaining + */ + fun setMethodTCP(): PortScan { + setMethod(METHOD_TCP) + return this + } + + /** Cancel a running ping */ + fun cancel() { + cancelled = true + runningFlowable.unsubscribeOn(Schedulers.computation()) + } + + /** + * Perform a synchronous (blocking) port scan and return a list of open ports + * + * @return - ping result + */ + fun doScan(): List { + cancelled = false + openPortsFound.clear() + runningFlowable = createPortScanFlowable().doOnComplete { + openPortsFound.sort() + } + runningFlowable.blockingSubscribe() + return openPortsFound + } + + private fun createPortScanFlowable(): Flowable { + return Flowable.fromIterable(ports) + .parallel(noThreads) + .runOn(Schedulers.io()) + .map { portNo -> + PortScanRunnable(address, portNo, timeOutMillis, method).run() + }.sequential() + .subscribeOn(Schedulers.computation()) + } + + @Synchronized + private fun portScanned(port: Int, open: Boolean) { + if (open) { + openPortsFound.add(port) + } + portListener?.onResult(port, open) + } + + private inner class PortScanRunnable constructor( + private val address: InetAddress?, + private val portNo: Int, + private val timeOutMillis: Int, + private val method: Int + ) : Runnable { + override fun run() { + if (cancelled) return + when (method) { + METHOD_UDP -> portScanned( + portNo, + PortScanUDP.scanAddress(address, portNo, timeOutMillis) + ) + METHOD_TCP -> portScanned( + portNo, + PortScanTCP.scanAddress(address, portNo, timeOutMillis) + ) + else -> throw IllegalArgumentException("Invalid method") + } + } + } + + companion object { + private const val TIMEOUT_LOCALHOST = 25 + private const val TIMEOUT_LOCALNETWORK = 1000 + private const val TIMEOUT_REMOTE = 2500 + private const val DEFAULT_THREADS_LOCALHOST = 7 + private const val DEFAULT_THREADS_LOCALNETWORK = 50 + private const val DEFAULT_THREADS_REMOTE = 50 + private const val METHOD_TCP = 0 + private const val METHOD_UDP = 1 + + /** + * Set the address to ping + * + * @param address - Address to be pinged + * @return this object to allow chaining + * @throws UnknownHostException - if no IP address for the `host` could be found, or if a + * scope_id was specified for a global IPv6 address. + */ + @JvmStatic + @Throws(UnknownHostException::class) + fun onAddress(address: String?): PortScan { + return onAddress(InetAddress.getByName(address)) + } + + /** + * Set the address to ping + * + * @param ia - Address to be pinged + * @return this object to allow chaining + */ + @JvmStatic + fun onAddress(ia: InetAddress): PortScan { + val portScan = PortScan() + portScan.setAddress(ia) + portScan.setDefaultThreadsAndTimeouts() + return portScan + } + } +} diff --git a/portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanTCP.kt b/portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanTCP.kt new file mode 100644 index 0000000000..8c816b155d --- /dev/null +++ b/portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanTCP.kt @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.stealthcopter.networktools.portscanning + +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket + +/** + * Created by mat on 13/12/15. + */ +object PortScanTCP { + /** + * Check if a port is open with TCP + * + * @param ia - address to scan + * @param portNo - port to scan + * @param timeoutMillis - timeout + * @return - true if port is open, false if not or unknown + */ + @JvmStatic + @Suppress("LabeledExpression") + fun scanAddress(ia: InetAddress?, portNo: Int, timeoutMillis: Int): Boolean { + return Socket().let { s -> + runCatching { + s.connect(InetSocketAddress(ia, portNo), timeoutMillis) + return@let true + }.also { + runCatching { + s.close() + }.getOrNull() + }.getOrDefault(false) + } + } +} diff --git a/portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanUDP.kt b/portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanUDP.kt new file mode 100644 index 0000000000..286d6e7e01 --- /dev/null +++ b/portscanner/src/main/java/com/stealthcopter/networktools/portscanning/PortScanUDP.kt @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.stealthcopter.networktools.portscanning + +import java.lang.Exception +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetAddress +import java.net.SocketTimeoutException + +/** + * Created by mat on 13/12/15. + */ +object PortScanUDP { + /** + * Check if a port is open with UDP, note that this isn't reliable + * as UDP will does not send ACKs + * + * @param ia - address to scan + * @param portNo - port to scan + * @param timeoutMillis - timeout + * @return - true if port is open, false if not or unknown + */ + @JvmStatic + fun scanAddress(ia: InetAddress?, portNo: Int, timeoutMillis: Int): Boolean { + try { + val bytes = ByteArray(128) + val dp = DatagramPacket(bytes, bytes.size) + val ds = DatagramSocket() + ds.soTimeout = timeoutMillis + ds.connect(ia, portNo) + ds.send(dp) + ds.isConnected + ds.receive(dp) + ds.close() + } catch (e: SocketTimeoutException) { + return true + } catch (ignore: Exception) { + } + return false + } +} diff --git a/settings.gradle b/settings.gradle index 4f9571f586..70edc43f9d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ include ':file_operations' +include ':portscanner' include ':app', ':commons_compress_7z' diff --git a/testShared/src/test/java/com/amaze/filemanager/shadows/ShadowSmbUtil.kt b/testShared/src/test/java/com/amaze/filemanager/shadows/ShadowSmbUtil.kt index cdc2551c50..b141acd655 100644 --- a/testShared/src/test/java/com/amaze/filemanager/shadows/ShadowSmbUtil.kt +++ b/testShared/src/test/java/com/amaze/filemanager/shadows/ShadowSmbUtil.kt @@ -21,7 +21,7 @@ package com.amaze.filemanager.shadows import android.content.Context -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import jcifs.context.SingletonContext import jcifs.smb.SmbException import jcifs.smb.SmbFile diff --git a/testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt b/testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt new file mode 100644 index 0000000000..f859a35b43 --- /dev/null +++ b/testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt @@ -0,0 +1,51 @@ +package com.amaze.filemanager.test.volley + +import com.android.volley.AuthFailureError +import com.android.volley.Request +import com.android.volley.toolbox.BaseHttpStack +import com.android.volley.toolbox.HttpResponse +import java.io.IOException + +/** + * Mock [BaseHttpStack] for test only. + */ +class MockHttpStack : BaseHttpStack() { + + private lateinit var mResponseToReturn: HttpResponse + private lateinit var lastUrl: String + private lateinit var mLastHeaders: MutableMap + private var lastPostBody: ByteArray? = null + + /** + * get headers in last request + */ + fun getLastHeaders() = mLastHeaders + + /** + * Manually set response to return + */ + fun setResponseToReturn(response: HttpResponse) { + mResponseToReturn = response + } + + @Throws(IOException::class, AuthFailureError::class) + override fun executeRequest( + request: Request<*>, + additionalHeaders: Map? + ): HttpResponse { + lastUrl = request.url + mLastHeaders = HashMap() + if (request.headers != null) { + mLastHeaders.putAll(request.headers) + } + if (additionalHeaders != null) { + mLastHeaders.putAll(additionalHeaders) + } + try { + lastPostBody = request.body + } catch (e: AuthFailureError) { + lastPostBody = null + } + return mResponseToReturn + } +} From 72d2774c1cd368e4649603b12afe94c071ead73e Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 6 Jun 2023 22:54:35 +0530 Subject: [PATCH 17/58] move basic search off of main thread Signed-off-by: VishnuSanal --- .../ui/activities/MainActivityViewModel.kt | 38 +++++++++++++++++ .../ui/views/appbar/SearchView.java | 41 ++++++------------- 2 files changed, 51 insertions(+), 28 deletions(-) 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 dc01bebcfd..bc8579a436 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 @@ -23,10 +23,17 @@ package com.amaze.filemanager.ui.activities import android.app.Application import androidx.collection.LruCache import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import androidx.preference.PreferenceManager import com.amaze.filemanager.adapters.data.LayoutElementParcelable +import com.amaze.filemanager.fileoperations.filesystem.OpenMode +import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles +import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.util.Locale class MainActivityViewModel(val applicationContext: Application) : AndroidViewModel(applicationContext) { @@ -72,4 +79,35 @@ class MainActivityViewModel(val applicationContext: Application) : fun getFromMediaFilesCache(mediaType: Int): List? { return mediaCacheHash[mediaType] } + + fun basicSearch(s: String, mainActivity: MainActivity) : MutableLiveData> { + + val hybridFileParcelables = ArrayList() + + val mutableLiveData: MutableLiveData> = MutableLiveData(hybridFileParcelables) + + val showHiddenFiles = PreferenceManager + .getDefaultSharedPreferences(mainActivity) + .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) + + viewModelScope.launch(Dispatchers.IO) { + listFiles( + mainActivity.currentMainFragment!!.currentPath!!, + mainActivity.isRootExplorer, + showHiddenFiles, + { _: OpenMode? -> null } + ) { hybridFileParcelable: HybridFileParcelable -> + if (hybridFileParcelable.getName(mainActivity) + .lowercase(Locale.getDefault()) + .contains(s.lowercase(Locale.getDefault())) + && (showHiddenFiles || !hybridFileParcelable.isHidden)) { + hybridFileParcelables.add(hybridFileParcelable) + + mutableLiveData.postValue(hybridFileParcelables) + } + } + } + + return mutableLiveData + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index f9a2b3e1a3..0aabf3c3a4 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -32,7 +32,6 @@ import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; import com.amaze.filemanager.filesystem.HybridFileParcelable; import com.amaze.filemanager.filesystem.RootHelper; -import com.amaze.filemanager.filesystem.root.ListFilesCommand; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.theme.AppTheme; @@ -208,14 +207,14 @@ private boolean onSearch(boolean shouldSave) { return false; } - search(s); + basicSearch(s); if (shouldSave) saveRecentPreference(s); return true; } - private void search(String s) { + private void basicSearch(String s) { clearRecyclerView(); @@ -228,30 +227,16 @@ private void search(String s) { mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_indexed_search))); - ArrayList hybridFileParcelables = new ArrayList<>(); - - boolean showHiddenFiles = - PreferenceManager.getDefaultSharedPreferences(mainActivity) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); - - // TODO: takes too much resources & freezes main thread on huge folders - ListFilesCommand.INSTANCE.listFiles( - mainActivity.getCurrentMainFragment().getPath(), - mainActivity.isRootExplorer(), - showHiddenFiles, - mode -> null, - hybridFileParcelable -> { - if (hybridFileParcelable.getName(mainActivity).toLowerCase().contains(s.toLowerCase()) - && (showHiddenFiles || !hybridFileParcelable.isHidden())) { - - hybridFileParcelables.add(hybridFileParcelable); - - searchRecyclerViewAdapter.submitList(hybridFileParcelables); - - searchRecyclerViewAdapter.notifyItemInserted(hybridFileParcelables.size() + 1); - } - return null; - }); + mainActivity + .getCurrentMainFragment() + .getMainActivityViewModel() + .basicSearch(s, mainActivity) + .observe( + mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), + hybridFileParcelables -> { + searchRecyclerViewAdapter.submitList(hybridFileParcelables); + searchRecyclerViewAdapter.notifyItemInserted(hybridFileParcelables.size() + 1); + }); } private void saveRecentPreference(String s) { @@ -315,7 +300,7 @@ private void initRecentSearches(Context context) { Utils.hideKeyboard(mainActivity); - search(s); + basicSearch(s); }); } } From db1380718910e59915b7ce7c27ad728749f2a4ee Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 6 Jun 2023 22:56:10 +0530 Subject: [PATCH 18/58] chore: spotless Signed-off-by: VishnuSanal --- .../ui/activities/MainActivityViewModel.kt | 8 ++++---- .../filemanager/ui/views/appbar/SearchView.java | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) 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 bc8579a436..aa7c537759 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 @@ -80,8 +80,7 @@ class MainActivityViewModel(val applicationContext: Application) : return mediaCacheHash[mediaType] } - fun basicSearch(s: String, mainActivity: MainActivity) : MutableLiveData> { - + fun basicSearch(s: String, mainActivity: MainActivity): MutableLiveData> { val hybridFileParcelables = ArrayList() val mutableLiveData: MutableLiveData> = MutableLiveData(hybridFileParcelables) @@ -99,8 +98,9 @@ class MainActivityViewModel(val applicationContext: Application) : ) { hybridFileParcelable: HybridFileParcelable -> if (hybridFileParcelable.getName(mainActivity) .lowercase(Locale.getDefault()) - .contains(s.lowercase(Locale.getDefault())) - && (showHiddenFiles || !hybridFileParcelable.isHidden)) { + .contains(s.lowercase(Locale.getDefault())) && + (showHiddenFiles || !hybridFileParcelable.isHidden) + ) { hybridFileParcelables.add(hybridFileParcelable) mutableLiveData.postValue(hybridFileParcelables) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 0aabf3c3a4..53b44d7d15 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -228,12 +228,12 @@ private void basicSearch(String s) { mainActivity.getString(R.string.try_indexed_search))); mainActivity - .getCurrentMainFragment() - .getMainActivityViewModel() - .basicSearch(s, mainActivity) - .observe( - mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), - hybridFileParcelables -> { + .getCurrentMainFragment() + .getMainActivityViewModel() + .basicSearch(s, mainActivity) + .observe( + mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), + hybridFileParcelables -> { searchRecyclerViewAdapter.submitList(hybridFileParcelables); searchRecyclerViewAdapter.notifyItemInserted(hybridFileParcelables.size() + 1); }); From 7573836fadc375ddf11ae037d3175e3a1b3892af Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 6 Jun 2023 23:18:59 +0530 Subject: [PATCH 19/58] move indexed search off of main thread Signed-off-by: VishnuSanal --- .../ui/activities/MainActivityViewModel.kt | 58 ++++++++++++++++++- .../ui/views/appbar/SearchView.java | 51 ++++------------ 2 files changed, 67 insertions(+), 42 deletions(-) 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 aa7c537759..103aa2a643 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 @@ -21,6 +21,7 @@ package com.amaze.filemanager.ui.activities import android.app.Application +import android.provider.MediaStore import androidx.collection.LruCache import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData @@ -29,10 +30,12 @@ import androidx.preference.PreferenceManager import com.amaze.filemanager.adapters.data.LayoutElementParcelable import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFileParcelable +import com.amaze.filemanager.filesystem.RootHelper import com.amaze.filemanager.filesystem.root.ListFilesCommand.listFiles import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.io.File import java.util.Locale class MainActivityViewModel(val applicationContext: Application) : @@ -80,7 +83,7 @@ class MainActivityViewModel(val applicationContext: Application) : return mediaCacheHash[mediaType] } - fun basicSearch(s: String, mainActivity: MainActivity): MutableLiveData> { + fun basicSearch(mainActivity: MainActivity, query: String): MutableLiveData> { val hybridFileParcelables = ArrayList() val mutableLiveData: MutableLiveData> = MutableLiveData(hybridFileParcelables) @@ -98,7 +101,7 @@ class MainActivityViewModel(val applicationContext: Application) : ) { hybridFileParcelable: HybridFileParcelable -> if (hybridFileParcelable.getName(mainActivity) .lowercase(Locale.getDefault()) - .contains(s.lowercase(Locale.getDefault())) && + .contains(query.lowercase(Locale.getDefault())) && (showHiddenFiles || !hybridFileParcelable.isHidden) ) { hybridFileParcelables.add(hybridFileParcelable) @@ -110,4 +113,55 @@ class MainActivityViewModel(val applicationContext: Application) : return mutableLiveData } + + fun indexedSearch( + mainActivity: MainActivity, + query: String, + ): MutableLiveData< ArrayList > { + + val list = ArrayList() + + val mutableLiveData: MutableLiveData> = MutableLiveData(list) + + val showHiddenFiles = + PreferenceManager.getDefaultSharedPreferences(mainActivity) + .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) + + viewModelScope.launch(Dispatchers.IO) { + + val projection = arrayOf(MediaStore.Files.FileColumns.DATA) + + val cursor = mainActivity + .contentResolver + .query(MediaStore.Files.getContentUri("external"), projection, null, null, null) + ?: return@launch + + if (cursor.count > 0 && cursor.moveToFirst()) { + do { + val path = + cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)) + + if (path != null + && path.contains(mainActivity.currentMainFragment?.currentPath!!) + && File(path).name.lowercase(Locale.getDefault()).contains( + query.lowercase(Locale.getDefault()) + ) + ) { + + val hybridFileParcelable = + RootHelper.generateBaseFile(File(path), showHiddenFiles) + + if (hybridFileParcelable != null) { + list.add(hybridFileParcelable) + mutableLiveData.postValue(list) + } + } + } while (cursor.moveToNext()) + } + + cursor.close() + } + + return mutableLiveData + } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 53b44d7d15..4a513b2825 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -172,10 +172,16 @@ public void afterTextChanged(Editable s) {} if (searchMode == 1) { - List hybridFileParcelables = indexedSearch(mainActivity, s); - - searchRecyclerViewAdapter.submitList(hybridFileParcelables); - searchRecyclerViewAdapter.notifyDataSetChanged(); + mainActivity + .getCurrentMainFragment() + .getMainActivityViewModel() + .indexedSearch(mainActivity, s) + .observe( + mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), + hybridFileParcelables -> { + searchRecyclerViewAdapter.submitList(hybridFileParcelables); + searchRecyclerViewAdapter.notifyDataSetChanged(); + }); searchMode = 2; deepSearchTV.setText( @@ -230,7 +236,7 @@ private void basicSearch(String s) { mainActivity .getCurrentMainFragment() .getMainActivityViewModel() - .basicSearch(s, mainActivity) + .basicSearch(mainActivity, s) .observe( mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), hybridFileParcelables -> { @@ -305,41 +311,6 @@ private void initRecentSearches(Context context) { } } - private List indexedSearch(MainActivity mainActivity, String query) { - - ArrayList list = new ArrayList<>(); - final String[] projection = {MediaStore.Files.FileColumns.DATA}; - - Cursor cursor = - mainActivity - .getContentResolver() - .query(MediaStore.Files.getContentUri("external"), projection, null, null, null); - - if (cursor == null) return list; - else if (cursor.getCount() > 0 && cursor.moveToFirst()) { - do { - String path = - cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); - - if (path != null - && path.contains(mainActivity.getCurrentMainFragment().getPath()) - && new File(path).getName().toLowerCase().contains(query.toLowerCase())) { - - boolean showHiddenFiles = - PreferenceManager.getDefaultSharedPreferences(mainActivity) - .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false); - - HybridFileParcelable hybridFileParcelable = - RootHelper.generateBaseFile(new File(path), showHiddenFiles); - - if (hybridFileParcelable != null) list.add(hybridFileParcelable); - } - } while (cursor.moveToNext()); - } - cursor.close(); - return list; - } - /** show search view with a circular reveal animation */ public void revealSearchView() { final int START_RADIUS = 16; From febaf038d2ab8d138fd2830729454a976bd35942 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 6 Jun 2023 23:22:04 +0530 Subject: [PATCH 20/58] chore: spotless Signed-off-by: VishnuSanal --- .../ui/activities/MainActivityViewModel.kt | 13 ++++----- .../ui/views/appbar/SearchView.java | 27 +++++++------------ 2 files changed, 15 insertions(+), 25 deletions(-) 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 103aa2a643..eb90938ae4 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 @@ -116,9 +116,8 @@ class MainActivityViewModel(val applicationContext: Application) : fun indexedSearch( mainActivity: MainActivity, - query: String, - ): MutableLiveData< ArrayList > { - + query: String + ): MutableLiveData> { val list = ArrayList() val mutableLiveData: MutableLiveData> = MutableLiveData(list) @@ -128,7 +127,6 @@ class MainActivityViewModel(val applicationContext: Application) : .getBoolean(PREFERENCE_SHOW_HIDDENFILES, false) viewModelScope.launch(Dispatchers.IO) { - val projection = arrayOf(MediaStore.Files.FileColumns.DATA) val cursor = mainActivity @@ -141,13 +139,12 @@ class MainActivityViewModel(val applicationContext: Application) : val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)) - if (path != null - && path.contains(mainActivity.currentMainFragment?.currentPath!!) - && File(path).name.lowercase(Locale.getDefault()).contains( + if (path != null && + path.contains(mainActivity.currentMainFragment?.currentPath!!) && + File(path).name.lowercase(Locale.getDefault()).contains( query.lowercase(Locale.getDefault()) ) ) { - val hybridFileParcelable = RootHelper.generateBaseFile(File(path), showHiddenFiles) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 4a513b2825..b6ba9910a9 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -22,16 +22,11 @@ import static android.content.Context.INPUT_METHOD_SERVICE; import static android.os.Build.VERSION.SDK_INT; -import static com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants.PREFERENCE_SHOW_HIDDENFILES; -import java.io.File; import java.util.ArrayList; -import java.util.List; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.SearchRecyclerViewAdapter; -import com.amaze.filemanager.filesystem.HybridFileParcelable; -import com.amaze.filemanager.filesystem.RootHelper; import com.amaze.filemanager.ui.activities.MainActivity; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.theme.AppTheme; @@ -45,9 +40,7 @@ import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Context; -import android.database.Cursor; import android.graphics.PorterDuff; -import android.provider.MediaStore; import android.text.Editable; import android.text.TextWatcher; import android.view.ContextThemeWrapper; @@ -172,16 +165,16 @@ public void afterTextChanged(Editable s) {} if (searchMode == 1) { - mainActivity - .getCurrentMainFragment() - .getMainActivityViewModel() - .indexedSearch(mainActivity, s) - .observe( - mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), - hybridFileParcelables -> { - searchRecyclerViewAdapter.submitList(hybridFileParcelables); - searchRecyclerViewAdapter.notifyDataSetChanged(); - }); + mainActivity + .getCurrentMainFragment() + .getMainActivityViewModel() + .indexedSearch(mainActivity, s) + .observe( + mainActivity.getCurrentMainFragment().getViewLifecycleOwner(), + hybridFileParcelables -> { + searchRecyclerViewAdapter.submitList(hybridFileParcelables); + searchRecyclerViewAdapter.notifyDataSetChanged(); + }); searchMode = 2; deepSearchTV.setText( From 1cb7fd48a293f1306d46041ddf4c7889ee2e6b1f Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 6 Jun 2023 23:24:04 +0530 Subject: [PATCH 21/58] chore: spotless Signed-off-by: VishnuSanal --- .../ui/activities/MainActivityViewModel.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 eb90938ae4..32aecb7282 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 @@ -83,10 +83,13 @@ class MainActivityViewModel(val applicationContext: Application) : return mediaCacheHash[mediaType] } - fun basicSearch(mainActivity: MainActivity, query: String): MutableLiveData> { + fun basicSearch(mainActivity: MainActivity, query: String): + MutableLiveData> { val hybridFileParcelables = ArrayList() - val mutableLiveData: MutableLiveData> = MutableLiveData(hybridFileParcelables) + val mutableLiveData: + MutableLiveData> = + MutableLiveData(hybridFileParcelables) val showHiddenFiles = PreferenceManager .getDefaultSharedPreferences(mainActivity) @@ -100,8 +103,8 @@ class MainActivityViewModel(val applicationContext: Application) : { _: OpenMode? -> null } ) { hybridFileParcelable: HybridFileParcelable -> if (hybridFileParcelable.getName(mainActivity) - .lowercase(Locale.getDefault()) - .contains(query.lowercase(Locale.getDefault())) && + .lowercase(Locale.getDefault()) + .contains(query.lowercase(Locale.getDefault())) && (showHiddenFiles || !hybridFileParcelable.isHidden) ) { hybridFileParcelables.add(hybridFileParcelable) @@ -120,7 +123,9 @@ class MainActivityViewModel(val applicationContext: Application) : ): MutableLiveData> { val list = ArrayList() - val mutableLiveData: MutableLiveData> = MutableLiveData(list) + val mutableLiveData: MutableLiveData> = MutableLiveData( + list + ) val showHiddenFiles = PreferenceManager.getDefaultSharedPreferences(mainActivity) @@ -137,13 +142,15 @@ class MainActivityViewModel(val applicationContext: Application) : if (cursor.count > 0 && cursor.moveToFirst()) { do { val path = - cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)) + cursor.getString( + cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) + ) if (path != null && path.contains(mainActivity.currentMainFragment?.currentPath!!) && File(path).name.lowercase(Locale.getDefault()).contains( - query.lowercase(Locale.getDefault()) - ) + query.lowercase(Locale.getDefault()) + ) ) { val hybridFileParcelable = RootHelper.generateBaseFile(File(path), showHiddenFiles) From 402934a6fb6defacab4e6f76c76850c1594710ba Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 7 Jun 2023 21:14:56 +0530 Subject: [PATCH 22/58] minor change to app manager UI Signed-off-by: VishnuSanal --- .../java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt | 1 - .../java/com/amaze/filemanager/adapters/holders/AppHolder.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt index fa18dc62db..60460b7c2b 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/AppsRecyclerAdapter.kt @@ -68,7 +68,6 @@ import com.amaze.filemanager.utils.AnimUtils.marqueeAfterDelay import com.amaze.filemanager.utils.Utils import com.amaze.filemanager.utils.safeLet import java.io.File -import kotlin.collections.ArrayList import kotlin.math.roundToInt class AppsRecyclerAdapter( diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt index b14091a4c0..97bbd696b1 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt @@ -60,7 +60,7 @@ class AppHolder(view: View) : RecyclerView.ViewHolder(view) { packageName.visibility = View.VISIBLE val layoutParams = txtDesc.layoutParams as ViewGroup.MarginLayoutParams - layoutParams.setMargins(txtDesc.marginLeft, txtDesc.marginTop, 8, txtDesc.marginBottom) + layoutParams.setMargins(txtDesc.marginLeft, txtDesc.marginTop, 4, txtDesc.marginBottom) txtDesc.layoutParams = layoutParams view.findViewById(R.id.picture_icon).visibility = View.GONE From adbeec0475f6b74eef5074026d37099c956336ce Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 7 Jun 2023 23:46:13 +0530 Subject: [PATCH 23/58] codacy: functions missing docs Signed-off-by: VishnuSanal --- .../filemanager/ui/activities/MainActivityViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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 32aecb7282..81192a0d18 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 @@ -83,6 +83,9 @@ class MainActivityViewModel(val applicationContext: Application) : return mediaCacheHash[mediaType] } + /** + * Perform basic search: searches on the current directory + */ fun basicSearch(mainActivity: MainActivity, query: String): MutableLiveData> { val hybridFileParcelables = ArrayList() @@ -117,6 +120,9 @@ class MainActivityViewModel(val applicationContext: Application) : return mutableLiveData } + /** + * Perform indexed search: on MediaStore items from the current directory & it's children + */ fun indexedSearch( mainActivity: MainActivity, query: String From 3e7c53db85eb47dc9397c9bf8493023cf175590e Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Wed, 7 Jun 2023 23:52:33 +0530 Subject: [PATCH 24/58] fix: glitch when searching for the second time Signed-off-by: VishnuSanal --- .../ui/views/appbar/SearchView.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index b6ba9910a9..8664f1034c 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -107,14 +107,6 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener deepSearchTV = mainActivity.findViewById(R.id.searchDeepSearchTV); recyclerView = mainActivity.findViewById(R.id.searchRecyclerView); - searchMode = 0; - deepSearchTV.setText( - String.format( - "%s %s", - mainActivity.getString(R.string.not_finding_what_you_re_looking_for), - mainActivity.getString(R.string.try_indexed_search))); - deepSearchTV.setVisibility(View.GONE); - initRecentSearches(mainActivity); searchRecyclerViewAdapter = new SearchRecyclerViewAdapter(); @@ -304,11 +296,23 @@ private void initRecentSearches(Context context) { } } + private void resetSearchMode() { + searchMode = 0; + deepSearchTV.setText( + String.format( + "%s %s", + mainActivity.getString(R.string.not_finding_what_you_re_looking_for), + mainActivity.getString(R.string.try_indexed_search))); + deepSearchTV.setVisibility(View.GONE); + } + /** show search view with a circular reveal animation */ public void revealSearchView() { final int START_RADIUS = 16; int endRadius = Math.max(appbar.getToolbar().getWidth(), appbar.getToolbar().getHeight()); + resetSearchMode(); + Animator animator; if (SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { int[] searchCoords = new int[2]; From 82afd3bf81d6a411c42570cb8f4822b1f5bdec9f Mon Sep 17 00:00:00 2001 From: Hafis Date: Thu, 8 Jun 2023 20:56:01 +0530 Subject: [PATCH 25/58] first commit --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c5c444709..05e527963b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Please keep in mind the points below before considering contributing to Amaze: You won't be able to claim the license for changes made by you unless you do that. If there's no license header in any file, please include one from [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html) webpage. - Please follow [Android/JAVA code style](https://source.android.com/source/code-style.html) for writing any code, but do not use the Hungarian notation proposed - discussion [here](https://github.com/TeamAmaze/AmazeFileManager/issues/986). -Also, follow [Android Material Design guidelines](https://material.io/guidelines/material-design/introduction.html) in case you make changes to any UI element. +Also, follow [Android Material Design guidelines](https://m2.material.io/design/introduction) in case you make changes to any UI element. - To file a bug report, it is recommended to include the steps to reproduce it; and even better, it helps us a lot if you can capture the error messages in logcat too It is also recommended to enroll to our beta program from Play Store to test and verify any fix for the same. @@ -39,4 +39,4 @@ Finally: If we feel your PR is a significant help to us, we'll award you a bounty with any of your preferred mode of payment. Please provide the details for the same once asked. -Ready to roll? Start forking ;) \ No newline at end of file +Ready to roll? Start forking ;) From 0b0306e76b8c80fe078dd2463da2bee6b438df5b Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Thu, 8 Jun 2023 23:08:31 +0530 Subject: [PATCH 26/58] search: change "recursive" to "deep" Signed-off-by: VishnuSanal --- .../com/amaze/filemanager/ui/views/appbar/SearchView.java | 8 ++++---- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 8664f1034c..b3763b29c3 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -83,9 +83,9 @@ public class SearchView { private final SearchRecyclerViewAdapter searchRecyclerViewAdapter; - // 0 -> Basic - // 1 -> Indexed - // 2 -> Recursive + // 0 -> Basic Search + // 1 -> Indexed Search + // 2 -> Deep Search private int searchMode; private boolean enabled = false; @@ -173,7 +173,7 @@ public void afterTextChanged(Editable s) {} String.format( "%s %s", mainActivity.getString(R.string.not_finding_what_you_re_looking_for), - mainActivity.getString(R.string.try_recursive_search))); + mainActivity.getString(R.string.try_deep_search))); } else if (searchMode == 2) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0dcc2662e7..8e71c95b5a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -810,7 +810,7 @@ You only need to do this once, until the next time you select a new location for Cloud Connection credentials cleared Unfortunately, we were unable to migrate your cloud connection credentials to the new database schema, and we had to remove them from the app. Please create the cloud connection again. Not finding what you\'re looking for? - Try Recursive Search! + Try Deep Search! Try Indexed Search! Recent Results From e48a7808d1f0173ed5fd9751aa7dfd901350429b Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Thu, 8 Jun 2023 23:28:14 +0530 Subject: [PATCH 27/58] fix crash (seems like the issue was caused by not clearing the RecyclerView when a new dataset needs to be listed) (https://stackoverflow.com/q/35653439/9652621) Signed-off-by: VishnuSanal --- .../java/com/amaze/filemanager/ui/views/appbar/SearchView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index b3763b29c3..0f12d79c13 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -112,8 +112,6 @@ public SearchView(final AppBar appbar, MainActivity mainActivity, SearchListener searchRecyclerViewAdapter = new SearchRecyclerViewAdapter(); recyclerView.setAdapter(searchRecyclerViewAdapter); - clearRecyclerView(); - clearImageView.setOnClickListener( v -> { searchViewEditText.setText(""); @@ -312,6 +310,7 @@ public void revealSearchView() { int endRadius = Math.max(appbar.getToolbar().getWidth(), appbar.getToolbar().getHeight()); resetSearchMode(); + clearRecyclerView(); Animator animator; if (SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { From 27e5507c6a37444ef3ab7ed2967a8b753b3500be Mon Sep 17 00:00:00 2001 From: Hafis CP <75908249+CPHafis@users.noreply.github.com> Date: Fri, 9 Jun 2023 11:47:07 +0530 Subject: [PATCH 28/58] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05e527963b..2ed71c7119 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Please keep in mind the points below before considering contributing to Amaze: You won't be able to claim the license for changes made by you unless you do that. If there's no license header in any file, please include one from [GNU GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html) webpage. - Please follow [Android/JAVA code style](https://source.android.com/source/code-style.html) for writing any code, but do not use the Hungarian notation proposed - discussion [here](https://github.com/TeamAmaze/AmazeFileManager/issues/986). -Also, follow [Android Material Design guidelines](https://m2.material.io/design/introduction) in case you make changes to any UI element. +Also, follow [Android Material Design guidelines](https://m3.material.io/get-started) in case you make changes to any UI element. - To file a bug report, it is recommended to include the steps to reproduce it; and even better, it helps us a lot if you can capture the error messages in logcat too It is also recommended to enroll to our beta program from Play Store to test and verify any fix for the same. From ec00564acb0f97bc2d87eb3f19446490d5d14e6e Mon Sep 17 00:00:00 2001 From: Hafis CP <75908249+CPHafis@users.noreply.github.com> Date: Fri, 9 Jun 2023 11:48:01 +0530 Subject: [PATCH 29/58] Update CONTRIBUTING.md From 9be28fc73815c75dad5d58754cd43fbfa15ad3c5 Mon Sep 17 00:00:00 2001 From: Hafis CP <75908249+CPHafis@users.noreply.github.com> Date: Fri, 9 Jun 2023 20:50:28 +0530 Subject: [PATCH 30/58] Update CONTRIBUTING.md From b8f5c4ae80c5a1f53bade8a2ebf5976625834663 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Mon, 12 Jun 2023 00:19:51 +0530 Subject: [PATCH 31/58] move deep search TV to top Signed-off-by: VishnuSanal --- app/src/main/res/layout-v21/layout_search.xml | 42 +++++++++--------- .../main/res/layout-w720dp/layout_search.xml | 43 ++++++++++--------- app/src/main/res/layout/layout_search.xml | 42 +++++++++--------- 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/app/src/main/res/layout-v21/layout_search.xml b/app/src/main/res/layout-v21/layout_search.xml index ed8aaa8dcb..b5ce1cdc1b 100644 --- a/app/src/main/res/layout-v21/layout_search.xml +++ b/app/src/main/res/layout-v21/layout_search.xml @@ -81,9 +81,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" - android:padding="8dp" + android:paddingStart="8dp" + android:paddingTop="8dp" + android:paddingEnd="8dp" android:scrollbars="none" - app:layout_constraintBottom_toTopOf="@id/searchResultsHintTV" + app:layout_constraintBottom_toTopOf="@id/searchDeepSearchTV" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchRecentHintTV"> @@ -101,6 +103,22 @@ + + + app:layout_constraintTop_toBottomOf="@id/searchDeepSearchTV" /> - - + app:layout_constraintTop_toBottomOf="@id/searchResultsHintTV" /> \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp/layout_search.xml b/app/src/main/res/layout-w720dp/layout_search.xml index e7360f67db..177e166eb1 100644 --- a/app/src/main/res/layout-w720dp/layout_search.xml +++ b/app/src/main/res/layout-w720dp/layout_search.xml @@ -78,9 +78,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" - android:padding="8dp" + android:paddingStart="8dp" + android:paddingTop="8dp" + android:paddingEnd="8dp" android:scrollbars="none" - app:layout_constraintBottom_toTopOf="@id/searchResultsHintTV" + app:layout_constraintBottom_toTopOf="@id/searchDeepSearchTV" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchRecentHintTV"> @@ -98,6 +100,22 @@ + + - + app:layout_constraintTop_toBottomOf="@id/searchDeepSearchTV" /> - - + app:layout_constraintTop_toBottomOf="@id/searchResultsHintTV" /> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search.xml b/app/src/main/res/layout/layout_search.xml index f6c646f9d2..650b33bca0 100644 --- a/app/src/main/res/layout/layout_search.xml +++ b/app/src/main/res/layout/layout_search.xml @@ -78,9 +78,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" - android:padding="8dp" + android:paddingStart="8dp" + android:paddingTop="8dp" + android:paddingEnd="8dp" android:scrollbars="none" - app:layout_constraintBottom_toTopOf="@id/searchResultsHintTV" + app:layout_constraintBottom_toTopOf="@id/searchDeepSearchTV" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/searchRecentHintTV"> @@ -98,6 +100,22 @@ + + + app:layout_constraintTop_toBottomOf="@id/searchDeepSearchTV" /> - - + app:layout_constraintTop_toBottomOf="@id/searchResultsHintTV" /> \ No newline at end of file From f41e169f22a80e3a6313e9a18b59639a8ec06691 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Mon, 12 Jun 2023 00:34:52 +0530 Subject: [PATCH 32/58] apply accent color to deep search prompt Signed-off-by: VishnuSanal --- .../ui/views/appbar/SearchView.java | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 0f12d79c13..794c560025 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -41,8 +41,13 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PorterDuff; +import android.graphics.Typeface; import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableString; import android.text.TextWatcher; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewAnimationUtils; @@ -167,9 +172,9 @@ public void afterTextChanged(Editable s) {} }); searchMode = 2; + deepSearchTV.setText( - String.format( - "%s %s", + getSpannableText( mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_deep_search))); @@ -211,8 +216,7 @@ private void basicSearch(String s) { deepSearchTV.setVisibility(View.VISIBLE); searchMode = 1; deepSearchTV.setText( - String.format( - "%s %s", + getSpannableText( mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_indexed_search))); @@ -297,8 +301,7 @@ private void initRecentSearches(Context context) { private void resetSearchMode() { searchMode = 0; deepSearchTV.setText( - String.format( - "%s %s", + getSpannableText( mainActivity.getString(R.string.not_finding_what_you_re_looking_for), mainActivity.getString(R.string.try_indexed_search))); deepSearchTV.setVisibility(View.GONE); @@ -310,7 +313,7 @@ public void revealSearchView() { int endRadius = Math.max(appbar.getToolbar().getWidth(), appbar.getToolbar().getHeight()); resetSearchMode(); - clearRecyclerView(); + clearRecyclerView(); Animator animator; if (SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { @@ -461,6 +464,24 @@ private void clearRecyclerView() { searchResultsHintTV.setVisibility(View.GONE); } + private SpannableString getSpannableText(String s1, String s2) { + + SpannableString spannableString = new SpannableString(s1 + " " + s2); + + spannableString.setSpan( + new ForegroundColorSpan(mainActivity.getCurrentColorPreference().getAccent()), + s1.length() + 1, + spannableString.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannableString.setSpan( + new StyleSpan(Typeface.BOLD), + s1.length() + 1, + spannableString.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + return spannableString; + } + public interface SearchListener { void onSearch(String queue); } From e0a052ad78a6465464e77d1a03140ccb25451d91 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 13 Jun 2023 21:14:47 +0530 Subject: [PATCH 33/58] add to recents upon indexed search Signed-off-by: VishnuSanal --- .../java/com/amaze/filemanager/ui/views/appbar/SearchView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java index 794c560025..bbe3a77ac7 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java @@ -160,6 +160,8 @@ public void afterTextChanged(Editable s) {} if (searchMode == 1) { + saveRecentPreference(s); + mainActivity .getCurrentMainFragment() .getMainActivityViewModel() From df0eaadc0c4cf74c8fec6289e46f45c2bd08ddc3 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Tue, 13 Jun 2023 21:20:28 +0530 Subject: [PATCH 34/58] use the color view on the left to distinguish between a file & a directory Signed-off-by: VishnuSanal --- .../adapters/SearchRecyclerViewAdapter.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index ad17c6caa9..c523453385 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -69,14 +69,14 @@ class SearchRecyclerViewAdapter : holder.colorView.setBackgroundColor(getRandomColor(holder.colorView.context)) -// val colorPreference = -// (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference -// -// if (item != null && item.isDirectory) { // always false for some reason! -// holder.colorView.setBackgroundColor(colorPreference.iconSkin) -// } else { -// holder.colorView.setBackgroundColor(colorPreference.accent) -// } + val colorPreference = + (AppConfig.getInstance().mainActivityContext as MainActivity).currentColorPreference + + if (item.isDirectory) { + holder.colorView.setBackgroundColor(colorPreference.primaryFirstTab) + } else { + holder.colorView.setBackgroundColor(colorPreference.accent) + } } inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { From 354f3cfa2be4b61578158fbf8665f79b53314c53 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Wed, 14 Jun 2023 23:59:07 +0300 Subject: [PATCH 35/58] Refactoring ZipService: Migrated compressAsyncTask to RxJava chain --- .../asynchronous/services/ZipService.kt | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt index 43d4471d0a..537c5dfae0 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt @@ -24,7 +24,6 @@ import android.app.PendingIntent import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.* import android.net.Uri -import android.os.AsyncTask import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.O import android.os.IBinder @@ -45,6 +44,12 @@ import com.amaze.filemanager.ui.notifications.NotificationConstants import com.amaze.filemanager.utils.DatapointParcelable import com.amaze.filemanager.utils.ObtainableServiceBinder import com.amaze.filemanager.utils.ProgressHandler +import io.reactivex.Completable +import io.reactivex.CompletableEmitter +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.* @@ -62,7 +67,7 @@ class ZipService : AbstractProgressiveService() { private val log: Logger = LoggerFactory.getLogger(ZipService::class.java) private val mBinder: IBinder = ObtainableServiceBinder(this) - private lateinit var asyncTask: CompressAsyncTask + private val disposables = CompositeDisposable() private lateinit var mNotifyManager: NotificationManagerCompat private lateinit var mBuilder: NotificationCompat.Builder private var progressListener: ProgressListener? = null @@ -139,8 +144,8 @@ class ZipService : AbstractProgressiveService() { initNotificationViews() super.onStartCommand(intent, flags, startId) super.progressHalted() - asyncTask = CompressAsyncTask(this, baseFiles, mZipPath!!) - asyncTask.execute() + val zipTask = CompressTask(this, baseFiles, mZipPath!!) + disposables.add(zipTask.compress()) // If we get killed, after returning from here, restart return START_NOT_STICKY } @@ -170,59 +175,70 @@ class ZipService : AbstractProgressiveService() { override fun clearDataPackages() = dataPackages.clear() - inner class CompressAsyncTask( + inner class CompressTask( private val zipService: ZipService, private val baseFiles: ArrayList, private val zipPath: String - ) : AsyncTask() { + ) { private lateinit var zos: ZipOutputStream private lateinit var watcherUtil: ServiceWatcherUtil - private var totalBytes = 0L - - override fun doInBackground(vararg p1: Void): Void? { - // setting up service watchers and initial data packages - // finding total size on background thread (this is necessary condition for SMB!) - totalBytes = FileUtils.getTotalBytes(baseFiles, zipService.applicationContext) - progressHandler.sourceSize = baseFiles.size - progressHandler.totalSize = totalBytes - progressHandler.setProgressListener { speed: Long -> - publishResults(speed, false, false) + + fun compress(): Disposable { + return Completable.create { emitter -> + // setting up service watchers and initial data packages + // finding total size on background thread (this is necessary condition for SMB!) + val totalBytes = FileUtils.getTotalBytes(baseFiles, zipService.applicationContext) + progressHandler.sourceSize = baseFiles.size + progressHandler.totalSize = totalBytes + + progressHandler.setProgressListener { speed: Long -> + publishResults(speed, false, false) + } + zipService.addFirstDatapoint( + baseFiles[0].getName(applicationContext), + baseFiles.size, + totalBytes, + false + ) + execute( + emitter, + zipService.applicationContext, + FileUtils.hybridListToFileArrayList(baseFiles), + zipPath + ) + + emitter.onComplete() } - zipService.addFirstDatapoint( - baseFiles[0].getName(applicationContext), - baseFiles.size, - totalBytes, - false - ) - execute( - zipService.applicationContext, - FileUtils.hybridListToFileArrayList(baseFiles), - zipPath - ) - - return null + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + watcherUtil.stopWatch() + val intent = Intent(MainActivity.KEY_INTENT_LOAD_LIST) + .putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, zipPath) + zipService.sendBroadcast(intent) + zipService.stopSelf() + }, + { log.error(it.message ?: "ZipService.CompressAsyncTask.compress failed") } + ) } - override fun onCancelled() { - super.onCancelled() + fun cancel() { progressHandler.cancelled = true val zipFile = File(zipPath) if (zipFile.exists()) zipFile.delete() } - public override fun onPostExecute(a: Void?) { - watcherUtil.stopWatch() - val intent = Intent(MainActivity.KEY_INTENT_LOAD_LIST) - .putExtra(MainActivity.KEY_INTENT_LOAD_LIST_FILE, zipPath) - zipService.sendBroadcast(intent) - zipService.stopSelf() - } - /** * Main logic for zipping specified files. */ - fun execute(context: Context, baseFiles: ArrayList, zipPath: String?) { + fun execute( + emitter: CompletableEmitter, + context: Context, + baseFiles: ArrayList, + zipPath: String, + ) { val out: OutputStream? val zipDirectory = File(zipPath) watcherUtil = ServiceWatcherUtil(progressHandler) @@ -231,7 +247,7 @@ class ZipService : AbstractProgressiveService() { out = FileUtil.getOutputStream(zipDirectory, context) zos = ZipOutputStream(BufferedOutputStream(out)) for ((fileProgress, file) in baseFiles.withIndex()) { - if (isCancelled) return + if (emitter.isDisposed) return progressHandler.fileName = file.name progressHandler.sourceFilesProcessed = fileProgress + 1 compressFile(file, "") @@ -259,8 +275,8 @@ class ZipService : AbstractProgressiveService() { zos.putNextEntry(createZipEntry(file, path)) val buf = ByteArray(GenericCopyUtil.DEFAULT_BUFFER_SIZE) var len: Int - BufferedInputStream(FileInputStream(file)).use { `in` -> - while (`in`.read(buf).also { len = it } > 0) { + BufferedInputStream(FileInputStream(file)).use { bufferedInputStream -> + while (bufferedInputStream.read(buf).also { len = it } > 0) { if (!progressHandler.cancelled) { zos.write(buf, 0, len) ServiceWatcherUtil.position += len.toLong() @@ -312,6 +328,7 @@ class ZipService : AbstractProgressiveService() { override fun onDestroy() { super.onDestroy() unregisterReceiver(receiver1) + disposables.dispose() } companion object { From 4baa4fdb8e6117c6c2d0b0f2d064eebd5c313bcf Mon Sep 17 00:00:00 2001 From: Obolrom Date: Thu, 15 Jun 2023 15:40:32 +0300 Subject: [PATCH 36/58] Refactoring ZipService: fixed filepath warning --- .../com/amaze/filemanager/asynchronous/services/ZipService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt index 537c5dfae0..7b03b6a08e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt @@ -144,7 +144,7 @@ class ZipService : AbstractProgressiveService() { initNotificationViews() super.onStartCommand(intent, flags, startId) super.progressHalted() - val zipTask = CompressTask(this, baseFiles, mZipPath!!) + val zipTask = CompressTask(this, baseFiles, zipFile.absolutePath) disposables.add(zipTask.compress()) // If we get killed, after returning from here, restart return START_NOT_STICKY From 29f7e5762f6e00c28a58b4f024e5e9fc2f3a5e6b Mon Sep 17 00:00:00 2001 From: Obolrom Date: Thu, 15 Jun 2023 15:46:22 +0300 Subject: [PATCH 37/58] Refactoring ZipService: fixed spotless --- .../com/amaze/filemanager/asynchronous/services/ZipService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt index 7b03b6a08e..c7d3e2e382 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt @@ -237,7 +237,7 @@ class ZipService : AbstractProgressiveService() { emitter: CompletableEmitter, context: Context, baseFiles: ArrayList, - zipPath: String, + zipPath: String ) { val out: OutputStream? val zipDirectory = File(zipPath) From c28afd37b24400000e03b6b9770a93b72029072a Mon Sep 17 00:00:00 2001 From: Obolrom Date: Thu, 15 Jun 2023 19:06:00 +0300 Subject: [PATCH 38/58] Refactoring ZipService: added documentation for new methods --- .../amaze/filemanager/asynchronous/services/ZipService.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt index c7d3e2e382..2109d92461 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ZipService.kt @@ -184,6 +184,9 @@ class ZipService : AbstractProgressiveService() { private lateinit var zos: ZipOutputStream private lateinit var watcherUtil: ServiceWatcherUtil + /** + * Main use case for executing zipping task by given [zipPath] + */ fun compress(): Disposable { return Completable.create { emitter -> // setting up service watchers and initial data packages @@ -224,6 +227,9 @@ class ZipService : AbstractProgressiveService() { ) } + /** + * Deletes the destination file zip file if exists + */ fun cancel() { progressHandler.cancelled = true val zipFile = File(zipPath) From 6f52b0c515561a17959868f234afa2b3792c6e02 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sat, 24 Sep 2022 17:39:48 +0800 Subject: [PATCH 39/58] Subnetscanner v2 Fixes #2622 and fixes #3386. --- .../filemanager/ui/dialogs/SmbSearchDialog.kt | 15 ++++++++++++--- .../utils/smb/SameSubnetDiscoverDeviceStrategy.kt | 11 ++++++++++- .../utils/smb/SmbDeviceScannerObservable.kt | 6 +++++- .../filemanager/ui/activities/MainActivityTest.kt | 4 ++-- .../ui/dialogs/SmbConnectDialogTest.kt | 2 +- portscanner/src/main/AndroidManifest.xml | 8 +------- 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt index bd92520db8..014f5b5d3b 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt @@ -152,7 +152,12 @@ class SmbSearchDialog : DialogFragment() { * Called by [ComputerParcelableViewModel], add found computer to list view */ fun add(computer: ComputerParcelable) { - items.add(computer) + if (computer.addr == "-1" && computer.name == "-1") { + items.add(computer) + } else { + items.add(items.size - 1, computer) + removeDummy() + } notifyDataSetChanged() } @@ -161,7 +166,11 @@ class SmbSearchDialog : DialogFragment() { * (dummy) host */ fun removeDummy() { - items.removeFirst() + items.remove( + items.find { + it.addr == "-1" && it.name == "-1" + } + ) notifyDataSetChanged() } @@ -176,7 +185,7 @@ class SmbSearchDialog : DialogFragment() { * Answers if the list is empty = only has the dummy [ComputerParcelable] instance */ fun dummyOnly(): Boolean { - return items.size == 1 && items.first().addr == "-1" + return items.size == 1 && items.last().addr == "-1" } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt index 277150c647..d4385fe95a 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt @@ -80,7 +80,16 @@ class SameSubnetDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDevi it is InetAddress }.doOnNext { addr -> addr as InetAddress - callback.invoke(ComputerParcelable(addr.hostAddress, addr.hostName)) + callback.invoke( + ComputerParcelable( + addr.hostAddress, + if (addr.hostName == addr.hostAddress) { + addr.canonicalHostName + } else { + addr.hostName + } + ) + ) }.sequential().subscribe() } diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt index 14dcced9d8..0d4afd52c4 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbDeviceScannerObservable.kt @@ -20,6 +20,7 @@ package com.amaze.filemanager.utils.smb +import androidx.annotation.VisibleForTesting import com.amaze.filemanager.utils.ComputerParcelable import com.amaze.filemanager.utils.smb.SmbDeviceScannerObservable.DiscoverDeviceStrategy import io.reactivex.Observable @@ -50,11 +51,14 @@ class SmbDeviceScannerObservable : Observable() { fun onCancel() } - private var discoverDeviceStrategies: Array = + var discoverDeviceStrategies: Array = arrayOf( WsddDiscoverDeviceStrategy(), SameSubnetDiscoverDeviceStrategy() ) + @VisibleForTesting set + + @VisibleForTesting get private lateinit var observer: Observer diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt index 3743edfbca..79aeb00fa5 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt @@ -24,8 +24,8 @@ import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import com.amaze.filemanager.application.AppConfig -import com.amaze.filemanager.utils.SmbUtil.getSmbDecryptedPath -import com.amaze.filemanager.utils.SmbUtil.getSmbEncryptedPath +import com.amaze.filemanager.utils.smb.SmbUtil.getSmbDecryptedPath +import com.amaze.filemanager.utils.smb.SmbUtil.getSmbEncryptedPath import org.awaitility.Awaitility.await import org.junit.Assert.assertEquals import org.junit.Test diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt index e2d7a4be17..dd76331050 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt @@ -32,7 +32,7 @@ import com.amaze.filemanager.ui.dialogs.SmbConnectDialog.ARG_EDIT import com.amaze.filemanager.ui.dialogs.SmbConnectDialog.ARG_NAME import com.amaze.filemanager.ui.dialogs.SmbConnectDialog.ARG_PATH import com.amaze.filemanager.ui.dialogs.SmbConnectDialog.SmbConnectionListener -import com.amaze.filemanager.utils.SmbUtil +import com.amaze.filemanager.utils.smb.SmbUtil import io.mockk.confirmVerified import io.mockk.spyk import io.mockk.verify diff --git a/portscanner/src/main/AndroidManifest.xml b/portscanner/src/main/AndroidManifest.xml index 100ab263c3..568741e54f 100644 --- a/portscanner/src/main/AndroidManifest.xml +++ b/portscanner/src/main/AndroidManifest.xml @@ -1,8 +1,2 @@ - - - - - \ No newline at end of file + \ No newline at end of file From e536f9f37a64204748087822f51e2ae92d7c7d23 Mon Sep 17 00:00:00 2001 From: VishnuSanal Date: Fri, 23 Jun 2023 21:01:58 +0530 Subject: [PATCH 40/58] use Utils#dpToPx Signed-off-by: VishnuSanal --- .../com/amaze/filemanager/adapters/holders/AppHolder.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt index 97bbd696b1..f5ef96a37f 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt @@ -32,6 +32,7 @@ import androidx.core.view.marginTop import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.views.ThemedTextView +import com.amaze.filemanager.utils.Utils class AppHolder(view: View) : RecyclerView.ViewHolder(view) { @JvmField @@ -60,7 +61,12 @@ class AppHolder(view: View) : RecyclerView.ViewHolder(view) { packageName.visibility = View.VISIBLE val layoutParams = txtDesc.layoutParams as ViewGroup.MarginLayoutParams - layoutParams.setMargins(txtDesc.marginLeft, txtDesc.marginTop, 4, txtDesc.marginBottom) + layoutParams.setMargins( + txtDesc.marginLeft, + txtDesc.marginTop, + Utils.dpToPx(view.context, 4), + txtDesc.marginBottom + ) txtDesc.layoutParams = layoutParams view.findViewById(R.id.picture_icon).visibility = View.GONE From 6bd56f439e324ff60cd5eb5eb356dc1e52a86cd9 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sat, 24 Jun 2023 17:37:07 +0800 Subject: [PATCH 41/58] Adds Compress option to item popup menu Fixes #3766. --- .../com/amaze/filemanager/adapters/RecyclerAdapter.java | 5 ++++- .../java/com/amaze/filemanager/ui/ItemPopupMenu.java | 6 ++++++ .../filemanager/ui/dialogs/GeneralDialogCreation.java | 9 +++++++++ app/src/main/res/menu/item_extras.xml | 3 +++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index 047aadfba0..7e180188ae 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -1392,6 +1392,7 @@ private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelabl } } else { popupMenu.getMenu().findItem(R.id.book).setVisible(false); + popupMenu.getMenu().findItem(R.id.compress).setVisible(true); if (description.endsWith(fileExtensionZip) || description.endsWith(fileExtensionJar) @@ -1409,8 +1410,10 @@ private void showPopup(@NonNull View view, @NonNull final LayoutElementParcelabl || description.endsWith(fileExtensionGz) || description.endsWith(fileExtensionBzip2) || description.endsWith(fileExtensionLzma) - || description.endsWith(fileExtensionXz)) + || description.endsWith(fileExtensionXz)) { popupMenu.getMenu().findItem(R.id.ex).setVisible(true); + popupMenu.getMenu().findItem(R.id.compress).setVisible(false); + } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { diff --git a/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java b/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java index 1785f87d5a..a3ede65503 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java +++ b/app/src/main/java/com/amaze/filemanager/ui/ItemPopupMenu.java @@ -241,6 +241,12 @@ public void onButtonPressed(Intent intent, String password) utilitiesProvider, false); return true; + case R.id.compress: + GeneralDialogCreation.showCompressDialog( + mainActivity, + rowItem.generateBaseFile(), + mainActivity.getCurrentMainFragment().getMainFragmentViewModel().getCurrentPath()); + return true; case R.id.return_select: mainFragment.returnIntentResults(rowItem.generateBaseFile()); return true; diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java index c7e917c0a6..d952d3edde 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/GeneralDialogCreation.java @@ -867,6 +867,15 @@ public static void showArchiveDialog(final File f, final MainActivity m) { b.show(); } + public static void showCompressDialog( + @NonNull final MainActivity mainActivity, + final HybridFileParcelable baseFile, + final String current) { + ArrayList baseFiles = new ArrayList<>(); + baseFiles.add(baseFile); + showCompressDialog(mainActivity, baseFiles, current); + } + public static void showCompressDialog( @NonNull final MainActivity mainActivity, final ArrayList baseFiles, diff --git a/app/src/main/res/menu/item_extras.xml b/app/src/main/res/menu/item_extras.xml index acc508b5dc..d2d2d1201e 100644 --- a/app/src/main/res/menu/item_extras.xml +++ b/app/src/main/res/menu/item_extras.xml @@ -39,6 +39,9 @@ + From 5107b2ab9c98f37ab567aa266dac9bc54ac99c84 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Tue, 27 Jun 2023 23:09:38 +0800 Subject: [PATCH 42/58] Remove set drawer header background and Volley dependency We are used to allow setting the drawer's background image when we still had a Google+ community. Then we disabled it and the code was left cold and dead for quite a while. This PR removes the dead code and Volley dependency altogether, paving the way for OkHttp as the HTTP client library to use within Amaze, as well as the blockade towards implementing WebDAV client support(#376) --- app/build.gradle | 2 - .../filemanager/application/AppConfig.java | 17 ----- .../ui/activities/MainActivity.java | 6 +- .../filemanager/ui/views/drawer/Drawer.java | 52 --------------- .../filemanager/utils/LruBitmapCache.java | 63 ------------------- .../application/AppConfigTest.java | 13 ---- 6 files changed, 2 insertions(+), 151 deletions(-) delete mode 100644 app/src/main/java/com/amaze/filemanager/utils/LruBitmapCache.java diff --git a/app/build.gradle b/app/build.gradle index 34cd3e4b97..ecb040a502 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -187,8 +187,6 @@ dependencies { implementation 'org.greenrobot:eventbus:3.3.1' - implementation 'com.android.volley:volley:1.2.1' - implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" implementation "com.github.topjohnwu.libsu:io:${libsuVersion}" diff --git a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java index 09a33595f9..0c0c18379f 100644 --- a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java +++ b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java @@ -41,11 +41,7 @@ import com.amaze.filemanager.database.UtilsHandler; import com.amaze.filemanager.filesystem.ssh.CustomSshJConfig; import com.amaze.filemanager.ui.provider.UtilitiesProvider; -import com.amaze.filemanager.utils.LruBitmapCache; import com.amaze.filemanager.utils.ScreenUtils; -import com.android.volley.RequestQueue; -import com.android.volley.toolbox.ImageLoader; -import com.android.volley.toolbox.Volley; import android.app.Activity; import android.app.Application; @@ -71,8 +67,6 @@ public class AppConfig extends GlideApplication { private Logger log = null; private UtilitiesProvider utilsProvider; - private RequestQueue requestQueue; - private ImageLoader imageLoader; private UtilsHandler utilsHandler; private WeakReference mainActivityContext; @@ -201,17 +195,6 @@ public static synchronized AppConfig getInstance() { return instance; } - public ImageLoader getImageLoader() { - if (requestQueue == null) { - requestQueue = Volley.newRequestQueue(getApplicationContext()); - } - - if (imageLoader == null) { - this.imageLoader = new ImageLoader(requestQueue, new LruBitmapCache()); - } - return imageLoader; - } - public UtilsHandler getUtilsHandler() { return utilsHandler; } 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 04cd312614..d268f41ad1 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 @@ -1539,9 +1539,7 @@ public Drawer getDrawer() { protected void onActivityResult(int requestCode, int responseCode, Intent intent) { super.onActivityResult(requestCode, responseCode, intent); - if (requestCode == Drawer.image_selector_request_code) { - drawer.onActivityResult(requestCode, responseCode, intent); - } else if (requestCode == 3) { + if (requestCode == 3) { Uri treeUri; if (responseCode == Activity.RESULT_OK) { // Get Uri from Storage Access Framework. @@ -1721,7 +1719,7 @@ void initialiseViews() { if (getAppbar().getSearchView().isEnabled()) getAppbar().getSearchView().hideSearchView(); }); - drawer.setDrawerHeaderBackground(); + // drawer.setDrawerHeaderBackground(); } /** 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 c55fb95000..90a98a38a5 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 @@ -20,7 +20,6 @@ package com.amaze.filemanager.ui.views.drawer; -import static android.os.Build.VERSION.SDK_INT; 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; @@ -57,7 +56,6 @@ import com.amaze.filemanager.ui.fragments.CloudSheetFragment; import com.amaze.filemanager.ui.fragments.FtpServerFragment; import com.amaze.filemanager.ui.fragments.MainFragment; -import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.fragments.preferencefragments.QuickAccessesPrefsFragment; import com.amaze.filemanager.ui.theme.AppTheme; import com.amaze.filemanager.utils.Billing; @@ -68,8 +66,6 @@ import com.amaze.filemanager.utils.ScreenUtils; import com.amaze.filemanager.utils.TinyDB; import com.amaze.filemanager.utils.Utils; -import com.android.volley.VolleyError; -import com.android.volley.toolbox.ImageLoader; import com.cloudrail.si.interfaces.CloudStorage; import com.cloudrail.si.services.Box; import com.cloudrail.si.services.Dropbox; @@ -113,8 +109,6 @@ public class Drawer implements NavigationView.OnNavigationItemSelectedListener { private static final Logger LOG = LoggerFactory.getLogger(Drawer.class); - public static final int image_selector_request_code = 31; - public static final int STORAGES_GROUP = 0, SERVERS_GROUP = 1, CLOUDS_GROUP = 2, @@ -134,7 +128,6 @@ public class Drawer implements NavigationView.OnNavigationItemSelectedListener { private boolean isDrawerLocked = false; private FragmentTransaction pending_fragmentTransaction; private String pendingPath; - private ImageLoader mImageLoader; private String firstPath = null, secondPath = null; private DrawerLayout mDrawerLayout; @@ -185,8 +178,6 @@ public Drawer(MainActivity mainActivity) { return false; });*/ - mImageLoader = AppConfig.getInstance().getImageLoader(); - navView = mainActivity.findViewById(R.id.navigation); // set width of drawer in portrait to follow material guidelines @@ -742,23 +733,6 @@ private void addNewItem( } } - public void onActivityResult(int requestCode, int responseCode, Intent intent) { - if (mainActivity.getPrefs() != null && intent != null && intent.getData() != null) { - if (SDK_INT >= Build.VERSION_CODES.KITKAT) { - mainActivity - .getContentResolver() - .takePersistableUriPermission(intent.getData(), Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - mainActivity - .getPrefs() - .edit() - .putString( - PreferencesConstants.PREFERENCE_DRAWER_HEADER_PATH, intent.getData().toString()) - .commit(); - setDrawerHeaderBackground(); - } - } - public void closeIfNotLocked() { if (!isLocked()) { close(); @@ -921,32 +895,6 @@ public int getPhoneStorageCount() { return phoneStorageCount; } - public void setDrawerHeaderBackground() { - String path1 = - mainActivity.getPrefs().getString(PreferencesConstants.PREFERENCE_DRAWER_HEADER_PATH, null); - if (path1 == null) { - return; - } - try { - final ImageView headerImageView = new ImageView(mainActivity); - headerImageView.setImageDrawable(drawerHeaderParent.getBackground()); - mImageLoader.get( - path1, - new ImageLoader.ImageListener() { - @Override - public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) { - headerImageView.setImageBitmap(response.getBitmap()); - drawerHeaderView.setBackgroundResource(R.drawable.amaze_header_2); - } - - @Override - public void onErrorResponse(VolleyError error) {} - }); - } catch (Exception e) { - LOG.warn("failed to set drawer header background", e); - } - } - public void selectCorrectDrawerItemForPath(final String path) { Integer id = dataUtils.findLongestContainingDrawerItem(path); diff --git a/app/src/main/java/com/amaze/filemanager/utils/LruBitmapCache.java b/app/src/main/java/com/amaze/filemanager/utils/LruBitmapCache.java deleted file mode 100644 index 66e2103690..0000000000 --- a/app/src/main/java/com/amaze/filemanager/utils/LruBitmapCache.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.utils; - -import com.android.volley.toolbox.ImageLoader; - -import android.graphics.Bitmap; -import android.util.LruCache; - -/** Created by vishal on 7/6/16. */ -public class LruBitmapCache extends LruCache implements ImageLoader.ImageCache { - /** - * @param maxSize for caches that do not override {@link #sizeOf}, this is the maximum number of - * entries in the cache. For all other caches, this is the maximum sum of the sizes of the - * entries in this cache. - */ - public LruBitmapCache(int maxSize) { - super(maxSize); - } - - public LruBitmapCache() { - this(getDefaultCacheSize()); - } - - private static int getDefaultCacheSize() { - int memory = (int) (Runtime.getRuntime().maxMemory() / 1024); - return memory / 8; - } - - @Override - public Bitmap getBitmap(String url) { - return get(url); - } - - @Override - public void putBitmap(String url, Bitmap bitmap) { - put(url, bitmap); - } - - @Override - protected int sizeOf(String key, Bitmap value) { - - return value.getByteCount(); - } -} diff --git a/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java b/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java index b92d24725a..84ea2611b4 100644 --- a/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java +++ b/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java @@ -90,19 +90,6 @@ public void testToastWithString() { assertEquals("Hello world", ShadowToast.getTextOfLatestToast()); } - @Test - public void testGetImageLoader() throws Exception { - Field requestQueue = AppConfig.class.getDeclaredField("requestQueue"); - Field imageLoader = AppConfig.class.getDeclaredField("imageLoader"); - requestQueue.setAccessible(true); - imageLoader.setAccessible(true); - - assertNull(requestQueue.get(AppConfig.getInstance())); - assertNull(imageLoader.get(AppConfig.getInstance())); - - assertNotNull(AppConfig.getInstance().getImageLoader()); - } - @Test public void testGlideMemoryCategorySetToHigh() throws Exception { Field memoryCategory = Glide.class.getDeclaredField("memoryCategory"); From 230313292d6936321985574b337f40791c277744 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Sat, 1 Jul 2023 20:50:58 +0300 Subject: [PATCH 43/58] ExtractService minor code improvements --- .../asynchronous/services/ExtractService.java | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java index 48a0d9fff9..74f426a53e 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java @@ -24,7 +24,6 @@ import java.io.File; import java.io.IOException; -import java.lang.ref.WeakReference; import java.util.ArrayList; import org.apache.commons.compress.PasswordRequiredException; @@ -59,6 +58,7 @@ import android.widget.RemoteViews; import android.widget.Toast; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -70,15 +70,14 @@ public class ExtractService extends AbstractProgressiveService { private final IBinder mBinder = new ObtainableServiceBinder<>(this); // list of data packages,// to initiate chart in process viewer fragment - private ArrayList dataPackages = new ArrayList<>(); + private final ArrayList dataPackages = new ArrayList<>(); private NotificationManagerCompat mNotifyManager; private NotificationCompat.Builder mBuilder; - private ProgressHandler progressHandler = new ProgressHandler(); + private final ProgressHandler progressHandler = new ProgressHandler(); private ProgressListener progressListener; - private int accentColor; - private SharedPreferences sharedPreferences; private RemoteViews customSmallContentViews, customBigContentViews; + private @Nullable DoWork extractingAsyncTask; public static final String KEY_PATH_ZIP = "zip"; public static final String KEY_ENTRIES_ZIP = "entries"; @@ -98,8 +97,9 @@ public int onStartCommand(Intent intent, int flags, final int startId) { String[] entries = intent.getStringArrayExtra(KEY_ENTRIES_ZIP); mNotifyManager = NotificationManagerCompat.from(getApplicationContext()); - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - accentColor = + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + int accentColor = ((AppConfig) getApplication()) .getUtilsProvider() .getColorPreference() @@ -153,7 +153,8 @@ public int onStartCommand(Intent intent, int flags, final int startId) { super.onStartCommand(intent, flags, startId); super.progressHalted(); - new DoWork(this, progressHandler, file, extractPath, entries).execute(); + extractingAsyncTask = new DoWork(progressHandler, file, extractPath, entries); + extractingAsyncTask.execute(); return START_NOT_STICKY; } @@ -216,6 +217,9 @@ protected void clearDataPackages() { @Override public void onDestroy() { super.onDestroy(); + if (extractingAsyncTask != null) { + extractingAsyncTask.cancel(true); + } unregisterReceiver(receiver1); } @@ -228,21 +232,15 @@ private long getTotalSize(String filePath) { } public class DoWork extends AsyncTask { - private WeakReference extractService; private String[] entriesToExtract; - private String extractionPath, compressedPath; - private ProgressHandler progressHandler; + private String extractionPath; + private final String compressedPath; + private final ProgressHandler progressHandler; private ServiceWatcherUtil watcherUtil; private boolean paused = false; private boolean passwordProtected = false; - private DoWork( - ExtractService extractService, - ProgressHandler progressHandler, - String cpath, - String epath, - String[] entries) { - this.extractService = new WeakReference<>(extractService); + private DoWork(ProgressHandler progressHandler, String cpath, String epath, String[] entries) { this.progressHandler = progressHandler; compressedPath = cpath; extractionPath = epath; @@ -254,8 +252,7 @@ protected Boolean doInBackground(Void... p) { while (!isCancelled()) { if (paused) continue; - final ExtractService extractService = this.extractService.get(); - if (extractService == null) return null; + final ExtractService extractService = ExtractService.this; File f = new File(compressedPath); String extractDirName = CompressedHelper.getFileName(f.getName()); @@ -396,7 +393,7 @@ protected void onProgressUpdate(IOException... values) { (dialog, which) -> { EditText editText = dialog.getView().findViewById(R.id.singleedittext_input); ArchivePasswordCache.getInstance().put(compressedPath, editText.getText().toString()); - this.extractService.get().getDataPackages().clear(); + ExtractService.this.getDataPackages().clear(); this.paused = false; dialog.dismiss(); }, @@ -413,8 +410,7 @@ protected void onProgressUpdate(IOException... values) { @Override public void onPostExecute(Boolean hasInvalidEntries) { ArchivePasswordCache.getInstance().remove(compressedPath); - final ExtractService extractService = this.extractService.get(); - if (extractService == null) return; + final ExtractService extractService = ExtractService.this; // check whether watcherutil was initialized. It was not initialized when we got exception // in extracting the file @@ -453,7 +449,7 @@ private void toastOnParseError(IOException result) { * Class used for the client Binder. Because we know this service always runs in the same process * as its clients, we don't need to deal with IPC. */ - private BroadcastReceiver receiver1 = + private final BroadcastReceiver receiver1 = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { From 380f89d888a2f023fb326248a02ae75297e8dff4 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Sat, 1 Jul 2023 20:59:22 +0300 Subject: [PATCH 44/58] CopyService minor code improvements --- .../asynchronous/services/CopyService.java | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java index e83fdd4af7..fbb0ae012a 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/CopyService.java @@ -85,18 +85,12 @@ public class CopyService extends AbstractProgressiveService { private final IBinder mBinder = new ObtainableServiceBinder<>(this); private ServiceWatcherUtil watcherUtil; - private ProgressHandler progressHandler = new ProgressHandler(); + private final ProgressHandler progressHandler = new ProgressHandler(); private ProgressListener progressListener; // list of data packages, to initiate chart in process viewer fragment - private ArrayList dataPackages = new ArrayList<>(); - private int accentColor; - private SharedPreferences sharedPreferences; + private final ArrayList dataPackages = new ArrayList<>(); private RemoteViews customSmallContentViews, customBigContentViews; - private boolean isRootExplorer; - private long totalSize = 0L; - private int totalSourceFiles = 0; - @Override public void onCreate() { super.onCreate(); @@ -107,13 +101,13 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, final int startId) { Bundle b = new Bundle(); - isRootExplorer = intent.getBooleanExtra(TAG_IS_ROOT_EXPLORER, false); + boolean isRootExplorer = intent.getBooleanExtra(TAG_IS_ROOT_EXPLORER, false); ArrayList files = intent.getParcelableArrayListExtra(TAG_COPY_SOURCES); String targetPath = intent.getStringExtra(TAG_COPY_TARGET); int mode = intent.getIntExtra(TAG_COPY_OPEN_MODE, OpenMode.UNKNOWN.ordinal()); final boolean move = intent.getBooleanExtra(TAG_COPY_MOVE, false); - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); - accentColor = + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(c); + int accentColor = ((AppConfig) getApplication()) .getUtilsProvider() .getColorPreference() @@ -238,7 +232,7 @@ private class DoInBackground extends AsyncTask { boolean move; private Copy copy; private String targetPath; - private boolean isRootExplorer; + private final boolean isRootExplorer; private int sourceProgress = 0; private DoInBackground(boolean isRootExplorer) { @@ -251,8 +245,8 @@ protected Void doInBackground(Bundle... p1) { // setting up service watchers and initial data packages // finding total size on background thread (this is necessary condition for SMB!) - totalSize = FileUtils.getTotalBytes(sourceFiles, c); - totalSourceFiles = sourceFiles.size(); + long totalSize = FileUtils.getTotalBytes(sourceFiles, c); + int totalSourceFiles = sourceFiles.size(); progressHandler.setSourceSize(totalSourceFiles); progressHandler.setTotalSize(totalSize); @@ -435,7 +429,7 @@ public void execute( } } } else { - for (HybridFileParcelable f : sourceFiles) failedFOps.add(f); + failedFOps.addAll(sourceFiles); return; } @@ -455,7 +449,7 @@ void copyRoot(HybridFileParcelable sourceFile, HybridFile targetFile, boolean mo try { if (!move) { CopyFilesCommand.INSTANCE.copyFiles(sourceFile.getPath(), targetFile.getPath()); - } else if (move) { + } else { MoveFileCommand.INSTANCE.moveFile(sourceFile.getPath(), targetFile.getPath()); } ServiceWatcherUtil.position += sourceFile.getSize(); @@ -532,7 +526,7 @@ private void copyFiles( } } - private BroadcastReceiver receiver3 = + private final BroadcastReceiver receiver3 = new BroadcastReceiver() { @Override @@ -544,7 +538,6 @@ public void onReceive(Context context, Intent intent) { @Override public IBinder onBind(Intent arg0) { - // TODO Auto-generated method stub return mBinder; } } From e7cf56aad41f3dabf9b571503f59e3381cf84919 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Sat, 1 Jul 2023 21:01:25 +0300 Subject: [PATCH 45/58] DecryptService minor code improvements --- .../filemanager/asynchronous/services/DecryptService.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java index c05d326b7b..e392a607e9 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/DecryptService.java @@ -34,7 +34,6 @@ import com.amaze.filemanager.asynchronous.asynctasks.Task; import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -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; @@ -113,8 +112,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { .getCurrentUserColorPreferences(this, sharedPreferences) .getAccent(); - OpenMode openMode = - OpenMode.values()[intent.getIntExtra(TAG_OPEN_MODE, OpenMode.UNKNOWN.ordinal())]; notificationManager = NotificationManagerCompat.from(getApplicationContext()); Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(Intent.ACTION_MAIN); @@ -267,7 +264,7 @@ public Callable getTask() { // and the cache directory in case we're here because of the viewer try { new CryptUtil(context, baseFile, decryptPath, progressHandler, failedOps, password); - } catch (AESCrypt.DecryptFailureException e) { + } catch (AESCrypt.DecryptFailureException ignored) { } catch (Exception e) { LOG.error("Error decrypting " + baseFile.getPath(), e); @@ -295,7 +292,7 @@ public void onDestroy() { this.unregisterReceiver(cancelReceiver); } - private BroadcastReceiver cancelReceiver = + private final BroadcastReceiver cancelReceiver = new BroadcastReceiver() { @Override From 846684444b0723ad168382dbfd723166590c9443 Mon Sep 17 00:00:00 2001 From: Obolrom Date: Sat, 1 Jul 2023 21:02:43 +0300 Subject: [PATCH 46/58] EncryptService minor code improvements --- .../asynchronous/services/EncryptService.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java index 897b64a109..28e1a1e128 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/EncryptService.java @@ -33,7 +33,6 @@ import com.amaze.filemanager.asynchronous.asynctasks.Task; import com.amaze.filemanager.asynchronous.asynctasks.TaskKt; import com.amaze.filemanager.asynchronous.management.ServiceWatcherUtil; -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; @@ -77,20 +76,18 @@ public class EncryptService extends AbstractProgressiveService { private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; private Context context; - private IBinder mBinder = new ObtainableServiceBinder<>(this); - private ProgressHandler progressHandler = new ProgressHandler(); + private final IBinder binder = new ObtainableServiceBinder<>(this); + private final ProgressHandler progressHandler = new ProgressHandler(); private ProgressListener progressListener; // list of data packages, to initiate chart in process viewer fragment - private ArrayList dataPackages = new ArrayList<>(); + private final ArrayList dataPackages = new ArrayList<>(); private ServiceWatcherUtil serviceWatcherUtil; private long totalSize = 0L; private HybridFileParcelable baseFile; - private ArrayList failedOps = new ArrayList<>(); + private final ArrayList failedOps = new ArrayList<>(); private String targetFilename; - private int accentColor; private boolean useAesCrypt; private String password; - private SharedPreferences sharedPreferences; private RemoteViews customSmallContentViews, customBigContentViews; @Override @@ -110,16 +107,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (useAesCrypt) { password = intent.getStringExtra(TAG_PASSWORD); } - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - accentColor = + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + int accentColor = ((AppConfig) getApplication()) .getUtilsProvider() .getColorPreference() .getCurrentUserColorPreferences(this, sharedPreferences) .getAccent(); - OpenMode openMode = - OpenMode.values()[intent.getIntExtra(TAG_OPEN_MODE, OpenMode.UNKNOWN.ordinal())]; notificationManager = NotificationManagerCompat.from(getApplicationContext()); Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(Intent.ACTION_MAIN); @@ -284,7 +279,7 @@ public Callable getTask() { @Override public IBinder onBind(Intent intent) { - return mBinder; + return binder; } @Override @@ -293,7 +288,7 @@ public void onDestroy() { this.unregisterReceiver(cancelReceiver); } - private BroadcastReceiver cancelReceiver = + private final BroadcastReceiver cancelReceiver = new BroadcastReceiver() { @Override From 28e5b73fea9983dd2e2982d0d7adc587be6e4820 Mon Sep 17 00:00:00 2001 From: Vishal Nehra Date: Fri, 7 Jul 2023 12:07:52 +0530 Subject: [PATCH 47/58] Minor changes in smb search dialog --- .../java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt index 014f5b5d3b..0b07aae815 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbSearchDialog.kt @@ -96,7 +96,7 @@ class SmbSearchDialog : DialogFragment() { } builder.positiveText(R.string.use_custom_ip) builder.positiveColor(accentColor) - viewModel.valHolder.value = (ComputerParcelable("-1", "-1")) + viewModel.valHolder.postValue(ComputerParcelable("-1", "-1")) listViewAdapter = ListViewAdapter(requireActivity()) val observable = SmbDeviceScannerObservable() subnetScannerObserver = observable @@ -108,7 +108,7 @@ class SmbSearchDialog : DialogFragment() { .subscribe( { computer: ComputerParcelable -> if (!listViewAdapter.contains(computer)) { - viewModel.valHolder.value = computer + viewModel.valHolder.postValue(computer) } }, { err: Throwable -> @@ -156,7 +156,6 @@ class SmbSearchDialog : DialogFragment() { items.add(computer) } else { items.add(items.size - 1, computer) - removeDummy() } notifyDataSetChanged() } From 83f618411de094eae633cab1f01ee35b8b2629c5 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sun, 9 Jul 2023 12:33:51 +0800 Subject: [PATCH 48/58] Migrate removed Volley dependency to OkHttp To fix missing Volley dependency caused by #3872 when #3704 was still depends on Volley --- app/build.gradle | 3 + .../utils/smb/WsddDiscoverDeviceStrategy.kt | 61 ++++++++----------- build.gradle | 1 + .../filemanager/test/volley/MockHttpStack.kt | 51 ---------------- 4 files changed, 31 insertions(+), 85 deletions(-) delete mode 100644 testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt diff --git a/app/build.gradle b/app/build.gradle index eeafa75fb6..6d4e53e4a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -204,6 +204,9 @@ dependencies { //FTP implementation "commons-net:commons-net:$commonsNetVersion" + //OkHttp + implementation "com.squareup.okhttp3:okhttp:$okHttpVersion" + implementation "org.bouncycastle:bcpkix-jdk15on:$bouncyCastleVersion" implementation "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion" diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt index 33e1813c69..db5a1ab86f 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt @@ -23,12 +23,14 @@ package com.amaze.filemanager.utils.smb import androidx.annotation.VisibleForTesting import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.SLASH import com.amaze.filemanager.utils.ComputerParcelable import com.amaze.filemanager.utils.NetworkUtil -import com.android.volley.Response.ErrorListener -import com.android.volley.VolleyError -import com.android.volley.toolbox.StringRequest -import com.android.volley.toolbox.Volley +import okhttp3.Headers.Companion.toHeaders +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody import org.slf4j.Logger import org.slf4j.LoggerFactory import org.xmlpull.v1.XmlPullParser @@ -75,7 +77,6 @@ class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStra private val wsdRequestHeaders = mutableMapOf( Pair("Accept-Encoding", "Identity"), - Pair("Content-Type", "application/soap+xml"), Pair("Connection", "Close"), Pair("User-Agent", "wsd") ) @@ -87,14 +88,10 @@ class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStra @VisibleForTesting set - private val queue = Volley.newRequestQueue(AppConfig.getInstance()) + private val queue = OkHttpClient() private var cancelled = false - init { - queue.start() - } - override fun discoverDevices(callback: (ComputerParcelable) -> Unit) { multicastForDevice { addr -> callback.invoke(addr) @@ -181,21 +178,30 @@ class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStra val endpoint = urn.substringAfter(URN_UUID) val dest = "http://${sourceAddress.hostAddress}:$TCP_PORT/$endpoint" - queue.add( - object : StringRequest( - Method.POST, - dest, - { resp -> + val requestBody = wsdRequestTemplate + .replace("##MESSAGE_ID##", "$URN_UUID$messageId") + .replace("##DEST_UUID##", urn) + .replace("##MY_UUID##", "$URN_UUID$tempDeviceId") + .toRequestBody("application/soap+xml".toMediaType()) + queue.newCall( + Request.Builder() + .url(dest) + .post(requestBody) + .headers(wsdRequestHeaders.toHeaders()) + .build() + ).execute().use { resp -> + if (resp.isSuccessful && resp.body != null) { + resp.body?.run { if (log.isTraceEnabled) log.trace("Response: $resp") val values = parseXmlForResponse( - resp, + this.string(), arrayOf(WSDP_TYPES, WSA_ADDRESS, PUB_COMPUTER) ) if (PUB_COMPUTER == values[WSDP_TYPES] && urn == values[WSA_ADDRESS]) { if (true == values[PUB_COMPUTER]?.isNotEmpty()) { val computerName: String = values[PUB_COMPUTER].let { - if (it!!.contains('/')) { - it.substringBefore("/") + if (it!!.contains(SLASH)) { + it.substringBefore(SLASH) } else { it } @@ -205,29 +211,16 @@ class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStra ) } } - }, - object : ErrorListener { - override fun onErrorResponse(error: VolleyError?) { - log.error("Error querying endpoint", error) - } } - ) { - override fun getBody(): ByteArray { - return wsdRequestTemplate - .replace("##MESSAGE_ID##", "$URN_UUID$messageId") - .replace("##DEST_UUID##", urn) - .replace("##MY_UUID##", "$URN_UUID$tempDeviceId") - .toByteArray(Charsets.UTF_8) - } - override fun getHeaders(): MutableMap = wsdRequestHeaders + } else { + log.error("Error querying endpoint", resp) } - ) + } } } override fun onCancel() { cancelled = true - queue.stop() } private fun parseXmlForResponse(xml: ByteArray, tags: Array) = diff --git a/build.gradle b/build.gradle index 3fd0ef3566..3d018b2808 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,7 @@ buildscript { jsoupVersion = "1.13.1" rxAndroidVersion = "2.1.1" rxJavaVersion = "2.2.9" + okHttpVersion = "4.9.0" } repositories { google() diff --git a/testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt b/testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt deleted file mode 100644 index f859a35b43..0000000000 --- a/testShared/src/test/java/com/amaze/filemanager/test/volley/MockHttpStack.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.amaze.filemanager.test.volley - -import com.android.volley.AuthFailureError -import com.android.volley.Request -import com.android.volley.toolbox.BaseHttpStack -import com.android.volley.toolbox.HttpResponse -import java.io.IOException - -/** - * Mock [BaseHttpStack] for test only. - */ -class MockHttpStack : BaseHttpStack() { - - private lateinit var mResponseToReturn: HttpResponse - private lateinit var lastUrl: String - private lateinit var mLastHeaders: MutableMap - private var lastPostBody: ByteArray? = null - - /** - * get headers in last request - */ - fun getLastHeaders() = mLastHeaders - - /** - * Manually set response to return - */ - fun setResponseToReturn(response: HttpResponse) { - mResponseToReturn = response - } - - @Throws(IOException::class, AuthFailureError::class) - override fun executeRequest( - request: Request<*>, - additionalHeaders: Map? - ): HttpResponse { - lastUrl = request.url - mLastHeaders = HashMap() - if (request.headers != null) { - mLastHeaders.putAll(request.headers) - } - if (additionalHeaders != null) { - mLastHeaders.putAll(additionalHeaders) - } - try { - lastPostBody = request.body - } catch (e: AuthFailureError) { - lastPostBody = null - } - return mResponseToReturn - } -} From 2612b829e55fcf66b865d4251ed0b1dbe7351633 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sat, 22 Jul 2023 16:00:36 +0800 Subject: [PATCH 49/58] Migrate widgets to use AppCompat versions where possible Fixes #3890 --- .../adapters/CompressedExplorerAdapter.java | 8 +- .../filemanager/adapters/RecyclerAdapter.java | 14 +- .../adapters/SearchRecyclerViewAdapter.kt | 6 +- .../glide/AppsAdapterPreloadModel.java | 4 +- .../filemanager/adapters/holders/AppHolder.kt | 18 +- .../holders/CompressedItemViewHolder.kt | 18 +- .../adapters/holders/DonationViewHolder.kt | 8 +- .../adapters/holders/HiddenViewHolder.kt | 10 +- .../adapters/holders/ItemViewHolder.kt | 28 +-- .../adapters/holders/SpecialViewHolder.kt | 4 +- .../asynctasks/CountItemsOrAndSizeTask.java | 6 +- .../hashcalculator/CalculateHashTask.kt | 8 +- .../asynctasks/movecopy/PrepareCopyTask.java | 4 +- .../asynchronous/services/ExtractService.java | 4 +- .../crashreport/ErrorActivity.java | 26 +-- .../filesystem/files/EncryptDecryptUtils.java | 4 +- .../com/amaze/filemanager/ui/Extensions.kt | 4 +- .../ui/activities/AboutActivity.java | 4 +- .../texteditor/TextEditorActivity.java | 14 +- .../ui/dialogs/ColorPickerDialog.java | 12 +- .../ui/dialogs/DecryptFingerprintDialog.kt | 6 +- .../ui/dialogs/DragAndDropDialog.kt | 10 +- .../ui/dialogs/GeneralDialogCreation.java | 66 +++--- .../ui/dialogs/SmbConnectDialog.java | 4 +- .../filemanager/ui/dialogs/SmbSearchDialog.kt | 10 +- .../ui/dialogs/share/ShareAdapter.java | 8 +- .../fragments/CompressedExplorerFragment.kt | 14 +- .../ui/fragments/DbViewerFragment.java | 4 +- .../ui/fragments/FtpServerFragment.kt | 22 +- .../ui/fragments/MainFragment.java | 20 +- .../filemanager/ui/fragments/TabFragment.java | 6 +- .../BookmarksPrefsFragment.kt | 7 +- .../ui/selection/SelectionPopupMenu.kt | 5 +- .../filemanager/ui/views/FastScroller.java | 4 +- .../filemanager/ui/views/ThemedTextView.java | 4 +- .../ui/views/WarnableTextInputValidator.java | 6 +- .../ui/views/appbar/BottomBar.java | 36 ++-- .../ui/views/appbar/SearchView.java | 14 +- .../views/drawer/ActionViewStateManager.java | 6 +- .../filemanager/ui/views/drawer/Drawer.java | 17 +- .../utils/MainActivityActionMode.kt | 5 +- .../filemanager/utils/MainActivityHelper.java | 19 +- .../com/amaze/filemanager/utils/Utils.java | 8 +- app/src/main/res/layout-v16/grid_header.xml | 4 +- app/src/main/res/layout-v16/list_header.xml | 4 +- app/src/main/res/layout-v21/layout_appbar.xml | 6 +- app/src/main/res/layout-v21/layout_search.xml | 8 +- .../res/layout-w500dp/properties_dialog.xml | 24 +-- .../main/res/layout-w720dp/layout_appbar.xml | 6 +- .../main/res/layout-w720dp/layout_search.xml | 8 +- .../main/res/layout-w720dp/main_toolbar.xml | 4 +- app/src/main/res/layout/actionmode.xml | 4 +- .../main/res/layout/actionmode_textviewer.xml | 10 +- app/src/main/res/layout/activity_about.xml | 194 +++++++++--------- app/src/main/res/layout/activity_error.xml | 30 +-- app/src/main/res/layout/bookmarkrow.xml | 5 +- app/src/main/res/layout/copy_dialog.xml | 6 +- ...log_decrypt_fingerprint_authentication.xml | 2 +- .../main/res/layout/dialog_twoedittexts.xml | 4 +- app/src/main/res/layout/drag_placeholder.xml | 4 +- app/src/main/res/layout/drawerheader.xml | 6 +- app/src/main/res/layout/fastscroller.xml | 13 +- app/src/main/res/layout/fragment_app_list.xml | 2 +- .../main/res/layout/fragment_db_viewer.xml | 2 +- app/src/main/res/layout/fragment_ftp.xml | 18 +- .../res/layout/fragment_open_file_dialog.xml | 12 +- .../main/res/layout/fragment_sheet_cloud.xml | 2 +- app/src/main/res/layout/grid_header.xml | 4 +- app/src/main/res/layout/griditem.xml | 9 +- app/src/main/res/layout/layout_appbar.xml | 6 +- .../res/layout/layout_draweractionview.xml | 2 +- app/src/main/res/layout/layout_search.xml | 6 +- app/src/main/res/layout/lexadrawer.xml | 8 +- app/src/main/res/layout/list_header.xml | 4 +- app/src/main/res/layout/main_frag.xml | 10 +- app/src/main/res/layout/main_toolbar.xml | 4 +- app/src/main/res/layout/permissiontable.xml | 32 +-- app/src/main/res/layout/processparent.xml | 9 +- app/src/main/res/layout/properties_audio.xml | 18 +- app/src/main/res/layout/properties_dialog.xml | 24 +-- .../main/res/layout/properties_document.xml | 62 +++--- .../main/res/layout/properties_general.xml | 30 +-- app/src/main/res/layout/properties_image.xml | 58 +++--- .../res/layout/properties_information.xml | 26 +-- app/src/main/res/layout/properties_video.xml | 18 +- app/src/main/res/layout/rowlayout.xml | 27 ++- app/src/main/res/layout/search.xml | 2 +- app/src/main/res/layout/search_row_item.xml | 4 +- app/src/main/res/layout/simplerow.xml | 4 +- app/src/main/res/layout/smb_computers_row.xml | 6 +- app/src/main/res/layout/smb_dialog.xml | 2 +- app/src/main/res/layout/snackbar_view.xml | 6 +- app/src/main/res/layout/tabfragment.xml | 4 +- .../res/layout/utilities_alias_layout.xml | 8 +- .../asynctasks/DbViewerTaskTest.java | 11 +- .../ssh/PemToKeyPairObservableRsaTest.kt | 4 +- .../ui/activities/TextEditorActivityTest.java | 4 +- .../views/WarnableTextInputValidatorTest.java | 6 +- 98 files changed, 660 insertions(+), 641 deletions(-) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java index 3917d23fb9..73b6e2bb07 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/CompressedExplorerAdapter.java @@ -53,10 +53,10 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.ImageButton; -import android.widget.ImageView; import android.widget.Toast; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.AppCompatImageView; import androidx.recyclerview.widget.RecyclerView; /** Created by Arpit on 25-01-2015 edited by Emmanuel Messulam */ @@ -127,7 +127,7 @@ public ArrayList getCheckedItemPositions() { * @param position the position of the item * @param imageView the circular {@link CircleGradientDrawable} that is to be animated */ - private void toggleChecked(int position, ImageView imageView) { + private void toggleChecked(int position, AppCompatImageView imageView) { compressedExplorerFragment.stopAnim(); stoppedAnimation = true; @@ -204,7 +204,7 @@ public CompressedItemViewHolder onCreateViewHolder(ViewGroup parent, int viewTyp } else if (viewType == TYPE_ITEM) { View v = mInflater.inflate(R.layout.rowlayout, parent, false); CompressedItemViewHolder vh = new CompressedItemViewHolder(v); - ImageButton about = v.findViewById(R.id.properties); + AppCompatImageButton about = v.findViewById(R.id.properties); about.setVisibility(View.INVISIBLE); return vh; } else { diff --git a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java index 7e180188ae..bffe1cdd6c 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/RecyclerAdapter.java @@ -84,15 +84,15 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.ImageView; import android.widget.PopupMenu; -import android.widget.TextView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.view.ActionMode; import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; import androidx.recyclerview.widget.RecyclerView; /** @@ -200,7 +200,7 @@ public RecyclerAdapter( * @param position the position of the item * @param imageView the check {@link CircleGradientDrawable} that is to be animated */ - public void toggleChecked(int position, ImageView imageView) { + public void toggleChecked(int position, AppCompatImageView imageView) { if (getItemsDigested().size() <= position || position < 0) { AppConfig.toast(context, R.string.operation_not_supported); return; @@ -1192,7 +1192,7 @@ private View getDragShadow(int selectionCount) { .setVisibility(View.VISIBLE); String rememberMovePreference = sharedPrefs.getString(PreferencesConstants.PREFERENCE_DRAG_AND_DROP_REMEMBERED, ""); - ImageView icon = + AppCompatImageView icon = mainFragment .getMainActivity() .getTabFragment() @@ -1204,7 +1204,7 @@ private View getDragShadow(int selectionCount) { .getTabFragment() .getDragPlaceholder() .findViewById(R.id.files_count_parent); - TextView filesCount = + AppCompatTextView filesCount = mainFragment .getMainActivity() .getTabFragment() @@ -1238,7 +1238,7 @@ private int getDragIconReference(String rememberMovePreference) { private void showThumbnailWithBackground( ItemViewHolder viewHolder, IconDataParcelable iconData, - ImageView view, + AppCompatImageView view, OnImageProcessed errorListener) { if (iconData.isImageBroken()) { viewHolder.genericIcon.setVisibility(View.VISIBLE); @@ -1301,7 +1301,7 @@ public boolean onResourceReady( private void showRoundedThumbnail( ItemViewHolder viewHolder, IconDataParcelable iconData, - ImageView view, + AppCompatImageView view, OnImageProcessed errorListener) { if (iconData.isImageBroken()) { View iconBackground = diff --git a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt index c523453385..d9ea4c87ce 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/SearchRecyclerViewAdapter.kt @@ -24,7 +24,7 @@ import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -81,8 +81,8 @@ class SearchRecyclerViewAdapter : inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val fileNameTV: TextView - val filePathTV: TextView + val fileNameTV: AppCompatTextView + val filePathTV: AppCompatTextView val colorView: View init { diff --git a/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java b/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java index 53d98db748..c76d130cfc 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java +++ b/app/src/main/java/com/amaze/filemanager/adapters/glide/AppsAdapterPreloadModel.java @@ -36,10 +36,10 @@ import android.content.Context; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; @@ -85,7 +85,7 @@ public RequestBuilder getPreloadRequestBuilder(String item) { } } - public void loadApkImage(String item, ImageView v) { + public void loadApkImage(String item, AppCompatImageView v) { if (isBottomSheet) { request.load(getApplicationIconFromPackageName(item)).into(v); } else { diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt index f5ef96a37f..71edf51820 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/AppHolder.kt @@ -22,10 +22,10 @@ package com.amaze.filemanager.adapters.holders import android.view.View import android.view.ViewGroup -import android.widget.ImageButton -import android.widget.ImageView import android.widget.RelativeLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView import androidx.core.view.marginBottom import androidx.core.view.marginLeft import androidx.core.view.marginTop @@ -36,7 +36,7 @@ import com.amaze.filemanager.utils.Utils class AppHolder(view: View) : RecyclerView.ViewHolder(view) { @JvmField - val apkIcon: ImageView = view.findViewById(R.id.apk_icon) + val apkIcon: AppCompatImageView = view.findViewById(R.id.apk_icon) @JvmField val txtTitle: ThemedTextView = view.findViewById(R.id.firstline) @@ -45,16 +45,16 @@ class AppHolder(view: View) : RecyclerView.ViewHolder(view) { val rl: RelativeLayout = view.findViewById(R.id.second) @JvmField - val txtDesc: TextView = view.findViewById(R.id.date) + val txtDesc: AppCompatTextView = view.findViewById(R.id.date) @JvmField - val about: ImageButton = view.findViewById(R.id.properties) + val about: AppCompatImageButton = view.findViewById(R.id.properties) @JvmField val summary: RelativeLayout = view.findViewById(R.id.summary) @JvmField - val packageName: TextView = view.findViewById(R.id.appManagerPackageName) + val packageName: AppCompatTextView = view.findViewById(R.id.appManagerPackageName) init { apkIcon.visibility = View.VISIBLE @@ -69,7 +69,7 @@ class AppHolder(view: View) : RecyclerView.ViewHolder(view) { ) txtDesc.layoutParams = layoutParams - view.findViewById(R.id.picture_icon).visibility = View.GONE - view.findViewById(R.id.generic_icon).visibility = View.GONE + view.findViewById(R.id.picture_icon).visibility = View.GONE + view.findViewById(R.id.generic_icon).visibility = View.GONE } } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt index a49cd50a0c..bf1dc4d78d 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/CompressedItemViewHolder.kt @@ -21,8 +21,8 @@ package com.amaze.filemanager.adapters.holders import android.view.View -import android.widget.ImageView -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.views.ThemedTextView @@ -30,28 +30,28 @@ import com.amaze.filemanager.ui.views.ThemedTextView class CompressedItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { // each data item is just a string in this case @JvmField - val pictureIcon: ImageView = view.findViewById(R.id.picture_icon) + val pictureIcon: AppCompatImageView = view.findViewById(R.id.picture_icon) @JvmField - val genericIcon: ImageView = view.findViewById(R.id.generic_icon) + val genericIcon: AppCompatImageView = view.findViewById(R.id.generic_icon) @JvmField - val apkIcon: ImageView = view.findViewById(R.id.apk_icon) + val apkIcon: AppCompatImageView = view.findViewById(R.id.apk_icon) @JvmField val txtTitle: ThemedTextView = view.findViewById(R.id.firstline) @JvmField - val txtDesc: TextView = view.findViewById(R.id.secondLine) + val txtDesc: AppCompatTextView = view.findViewById(R.id.secondLine) @JvmField - val date: TextView = view.findViewById(R.id.date) + val date: AppCompatTextView = view.findViewById(R.id.date) - val perm: TextView = view.findViewById(R.id.permis) + val perm: AppCompatTextView = view.findViewById(R.id.permis) @JvmField val rl: View = view.findViewById(R.id.second) @JvmField - val checkImageView: ImageView = view.findViewById(R.id.check_icon) + val checkImageView: AppCompatImageView = view.findViewById(R.id.check_icon) } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt index c2593c297f..21a7a223d6 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/DonationViewHolder.kt @@ -22,7 +22,7 @@ package com.amaze.filemanager.adapters.holders import android.view.View import android.widget.LinearLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R @@ -31,11 +31,11 @@ class DonationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val ROOT_VIEW: LinearLayout = itemView.findViewById(R.id.adapter_donation_root) @JvmField - val TITLE: TextView = itemView.findViewById(R.id.adapter_donation_title) + val TITLE: AppCompatTextView = itemView.findViewById(R.id.adapter_donation_title) @JvmField - val SUMMARY: TextView = itemView.findViewById(R.id.adapter_donation_summary) + val SUMMARY: AppCompatTextView = itemView.findViewById(R.id.adapter_donation_summary) @JvmField - val PRICE: TextView = itemView.findViewById(R.id.adapter_donation_price) + val PRICE: AppCompatTextView = itemView.findViewById(R.id.adapter_donation_price) } diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt index 0b19831a9f..23cdc016d9 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/HiddenViewHolder.kt @@ -21,9 +21,9 @@ package com.amaze.filemanager.adapters.holders import android.view.View -import android.widget.ImageButton import android.widget.LinearLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R @@ -34,13 +34,13 @@ import com.amaze.filemanager.R */ class HiddenViewHolder(view: View) : RecyclerView.ViewHolder(view) { @JvmField - val deleteButton: ImageButton = view.findViewById(R.id.delete_button) + val deleteButton: AppCompatImageButton = view.findViewById(R.id.delete_button) @JvmField - val textTitle: TextView = view.findViewById(R.id.filename) + val textTitle: AppCompatTextView = view.findViewById(R.id.filename) @JvmField - val textDescription: TextView = view.findViewById(R.id.file_path) + val textDescription: AppCompatTextView = view.findViewById(R.id.file_path) @JvmField val row: LinearLayout = view.findViewById(R.id.bookmarkrow) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt index 3681602210..a0e1685460 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/ItemViewHolder.kt @@ -21,10 +21,10 @@ package com.amaze.filemanager.adapters.holders import android.view.View -import android.widget.ImageButton -import android.widget.ImageView import android.widget.RelativeLayout -import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.views.ThemedTextView @@ -36,43 +36,43 @@ import com.amaze.filemanager.ui.views.ThemedTextView class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { // each data item is just a string in this case @JvmField - val pictureIcon: ImageView? = view.findViewById(R.id.picture_icon) + val pictureIcon: AppCompatImageView? = view.findViewById(R.id.picture_icon) @JvmField - val genericIcon: ImageView = view.findViewById(R.id.generic_icon) + val genericIcon: AppCompatImageView = view.findViewById(R.id.generic_icon) @JvmField - val apkIcon: ImageView? = view.findViewById(R.id.apk_icon) + val apkIcon: AppCompatImageView? = view.findViewById(R.id.apk_icon) @JvmField - val imageView1: ImageView? = view.findViewById(R.id.icon_thumb) + val imageView1: AppCompatImageView? = view.findViewById(R.id.icon_thumb) @JvmField val txtTitle: ThemedTextView = view.findViewById(R.id.firstline) @JvmField - val txtDesc: TextView = view.findViewById(R.id.secondLine) + val txtDesc: AppCompatTextView = view.findViewById(R.id.secondLine) @JvmField - val date: TextView = view.findViewById(R.id.date) + val date: AppCompatTextView = view.findViewById(R.id.date) @JvmField - val perm: TextView = view.findViewById(R.id.permis) + val perm: AppCompatTextView = view.findViewById(R.id.permis) @JvmField val baseItemView: View = view.findViewById(R.id.second) @JvmField - val genericText: TextView? = view.findViewById(R.id.generictext) + val genericText: AppCompatTextView? = view.findViewById(R.id.generictext) @JvmField - val about: ImageButton = view.findViewById(R.id.properties) + val about: AppCompatImageButton = view.findViewById(R.id.properties) @JvmField - val checkImageView: ImageView? = view.findViewById(R.id.check_icon) + val checkImageView: AppCompatImageView? = view.findViewById(R.id.check_icon) @JvmField - val checkImageViewGrid: ImageView? = view.findViewById(R.id.check_icon_grid) + val checkImageViewGrid: AppCompatImageView? = view.findViewById(R.id.check_icon_grid) @JvmField val iconLayout: RelativeLayout? = view.findViewById(R.id.icon_frame_grid) diff --git a/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt b/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt index ec4a3e128e..0fc7f10bb0 100644 --- a/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt +++ b/app/src/main/java/com/amaze/filemanager/adapters/holders/SpecialViewHolder.kt @@ -22,7 +22,7 @@ package com.amaze.filemanager.adapters.holders import android.content.Context import android.view.View -import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView import androidx.recyclerview.widget.RecyclerView import com.amaze.filemanager.R import com.amaze.filemanager.ui.provider.UtilitiesProvider @@ -39,7 +39,7 @@ class SpecialViewHolder( val type: Int ) : RecyclerView.ViewHolder(view) { // each data item is just a string in this case - private val txtTitle: TextView = view.findViewById(R.id.text) + private val txtTitle: AppCompatTextView = view.findViewById(R.id.text) companion object { const val HEADER_FILES = 0 diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java index 2319ef3c30..a6e63e1304 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/CountItemsOrAndSizeTask.java @@ -29,8 +29,8 @@ import android.content.Context; import android.os.AsyncTask; import android.text.format.Formatter; -import android.widget.TextView; +import androidx.appcompat.widget.AppCompatTextView; import androidx.core.util.Pair; /** @@ -39,12 +39,12 @@ public class CountItemsOrAndSizeTask extends AsyncTask, String> { private Context context; - private TextView itemsText; + private AppCompatTextView itemsText; private HybridFileParcelable file; private boolean isStorage; public CountItemsOrAndSizeTask( - Context c, TextView itemsText, HybridFileParcelable f, boolean storage) { + Context c, AppCompatTextView itemsText, HybridFileParcelable f, boolean storage) { this.context = c; this.itemsText = itemsText; file = f; diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt index 486c2cfacf..43e0dc27bb 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/hashcalculator/CalculateHashTask.kt @@ -23,8 +23,8 @@ package com.amaze.filemanager.asynchronous.asynctasks.hashcalculator import android.content.Context import android.view.View import android.widget.LinearLayout -import android.widget.TextView import android.widget.Toast +import androidx.appcompat.widget.AppCompatTextView import com.amaze.filemanager.R import com.amaze.filemanager.asynchronous.asynctasks.Task import com.amaze.filemanager.filesystem.HybridFileParcelable @@ -32,7 +32,7 @@ import com.amaze.filemanager.filesystem.files.FileUtils import org.slf4j.Logger import org.slf4j.LoggerFactory import java.lang.ref.WeakReference -import java.util.* +import java.util.Locale import java.util.concurrent.Callable data class Hash(val md5: String, val sha: String) @@ -78,8 +78,8 @@ class CalculateHashTask( val md5Text = hashes?.md5 ?: context.getString(R.string.unavailable) val shaText = hashes?.sha ?: context.getString(R.string.unavailable) - val md5HashText = view.findViewById(R.id.t9) - val sha256Text = view.findViewById(R.id.t10) + val md5HashText = view.findViewById(R.id.t9) + val sha256Text = view.findViewById(R.id.t10) val mMD5LinearLayout = view.findViewById(R.id.properties_dialog_md5) val mSHA256LinearLayout = view.findViewById(R.id.properties_dialog_sha256) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java index 4bd15f349a..9d06ddb19d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/movecopy/PrepareCopyTask.java @@ -52,10 +52,10 @@ import android.os.AsyncTask; import android.view.LayoutInflater; import android.view.View; -import android.widget.CheckBox; import android.widget.Toast; import androidx.annotation.IntDef; +import androidx.appcompat.widget.AppCompatCheckBox; /** * This AsyncTask works by creating a tree where each folder that can be fusioned together with @@ -222,7 +222,7 @@ private void showDialog( copyDialogBinding.fileNameText.setText(conflictingFiles.get(counter).getName(context.get())); // checkBox - final CheckBox checkBox = copyDialogBinding.checkBox; + final AppCompatCheckBox checkBox = copyDialogBinding.checkBox; Utils.setTint(context.get(), checkBox, accentColor); dialogBuilder.theme(mainActivity.get().getAppTheme().getMaterialDialogTheme(context.get())); dialogBuilder.title(context.get().getResources().getString(R.string.paste)); diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java index 74f426a53e..e836e9cb94 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ExtractService.java @@ -54,12 +54,12 @@ import android.os.AsyncTask; import android.os.IBinder; import android.text.TextUtils; -import android.widget.EditText; import android.widget.RemoteViews; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatEditText; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.preference.PreferenceManager; @@ -391,7 +391,7 @@ protected void onProgressUpdate(IOException... values) { R.string.archive_password_prompt, R.string.authenticate_password, (dialog, which) -> { - EditText editText = dialog.getView().findViewById(R.id.singleedittext_input); + AppCompatEditText editText = dialog.getView().findViewById(R.id.singleedittext_input); ArchivePasswordCache.getInstance().put(compressedPath, editText.getText().toString()); ExtractService.this.getDataPackages().clear(); this.paused = false; diff --git a/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java b/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java index ade83ff7b2..7921a34c40 100644 --- a/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java +++ b/app/src/main/java/com/amaze/filemanager/crashreport/ErrorActivity.java @@ -58,13 +58,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.Toolbar; import androidx.core.app.NavUtils; @@ -109,7 +109,7 @@ public class ErrorActivity extends ThemedActivity { private ErrorInfo errorInfo; private Class returnActivity; private String currentTimeStamp; - private EditText userCommentBox; + private AppCompatEditText userCommentBox; public static void reportError( final Context context, @@ -197,14 +197,14 @@ public void onCreate(final Bundle savedInstanceState) { actionBar.setDisplayShowTitleEnabled(true); } - final Button reportEmailButton = findViewById(R.id.errorReportEmailButton); - final Button reportTelegramButton = findViewById(R.id.errorReportTelegramButton); - final Button copyButton = findViewById(R.id.errorReportCopyButton); - final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton); + final AppCompatButton reportEmailButton = findViewById(R.id.errorReportEmailButton); + final AppCompatButton reportTelegramButton = findViewById(R.id.errorReportTelegramButton); + final AppCompatButton copyButton = findViewById(R.id.errorReportCopyButton); + final AppCompatButton reportGithubButton = findViewById(R.id.errorReportGitHubButton); userCommentBox = findViewById(R.id.errorCommentBox); - final TextView errorView = findViewById(R.id.errorView); - final TextView errorMessageView = findViewById(R.id.errorMessageView); + final AppCompatTextView errorView = findViewById(R.id.errorView); + final AppCompatTextView errorMessageView = findViewById(R.id.errorMessageView); returnActivity = MainActivity.class; errorInfo = intent.getParcelableExtra(ERROR_INFO); @@ -306,8 +306,8 @@ private void goToReturnActivity() { } private void buildInfo(final ErrorInfo info) { - final TextView infoLabelView = findViewById(R.id.errorInfoLabelsView); - final TextView infoView = findViewById(R.id.errorInfosView); + final AppCompatTextView infoLabelView = findViewById(R.id.errorInfoLabelsView); + final AppCompatTextView infoView = findViewById(R.id.errorInfosView); String text = ""; infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n")); @@ -440,7 +440,7 @@ private String getOsString() { private void addGuruMeditation() { // just an easter egg - final TextView sorryView = findViewById(R.id.errorSorryView); + final AppCompatTextView sorryView = findViewById(R.id.errorSorryView); String text = sorryView.getText().toString(); text += "\n" + getString(R.string.guru_meditation); sorryView.setText(text); diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java index b2418779c6..8744369041 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.java @@ -50,10 +50,10 @@ import android.content.SharedPreferences; import android.os.Build; import android.util.Base64; -import android.widget.EditText; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatEditText; import androidx.preference.PreferenceManager; /** @@ -114,7 +114,7 @@ public static void decryptFile( R.string.crypt_decrypt, R.string.authenticate_password, (dialog, which) -> { - EditText editText = dialog.getView().findViewById(R.id.singleedittext_input); + AppCompatEditText editText = dialog.getView().findViewById(R.id.singleedittext_input); decryptIntent.putExtra(EncryptService.TAG_PASSWORD, editText.getText().toString()); ServiceWatcherUtil.runService(main.getContext(), decryptIntent); dialog.dismiss(); 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 8b2b0f8013..98d28b1d7f 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/Extensions.kt @@ -27,8 +27,8 @@ import android.content.pm.PackageManager import android.text.TextUtils import android.view.View import android.view.inputmethod.InputMethodManager -import android.widget.EditText import android.widget.Toast +import androidx.appcompat.widget.AppCompatEditText import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig import com.google.android.material.textfield.TextInputLayout @@ -82,7 +82,7 @@ fun Context.updateAUAlias(shouldEnable: Boolean) { /** * Force keyboard pop up on focus */ -fun EditText.openKeyboard(context: Context) { +fun AppCompatEditText.openKeyboard(context: Context) { this.requestFocus() this.postDelayed( diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java index 5bd40d7128..7c35f30838 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/AboutActivity.java @@ -49,9 +49,9 @@ import android.os.Bundle; import android.view.MenuItem; import android.view.View; -import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.FileProvider; @@ -66,7 +66,7 @@ public class AboutActivity extends ThemedActivity implements View.OnClickListene private AppBarLayout mAppBarLayout; private CollapsingToolbarLayout mCollapsingToolbarLayout; - private TextView mTitleTextView; + private AppCompatTextView mTitleTextView; private View mAuthorsDivider, mDeveloper1Divider; private Billing billing; diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java index 51f86a430e..f3bebaf489 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/texteditor/TextEditorActivity.java @@ -70,20 +70,20 @@ import android.view.animation.AccelerateDecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.Toast; import androidx.annotation.ColorInt; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.AppCompatImageButton; import androidx.lifecycle.ViewModelProvider; public class TextEditorActivity extends ThemedActivity implements TextWatcher, View.OnClickListener { - public EditText mainTextView; - public EditText searchEditText; + public AppCompatEditText mainTextView; + public AppCompatEditText searchEditText; private Typeface inputTypefaceDefault; private Typeface inputTypefaceMono; private androidx.appcompat.widget.Toolbar toolbar; @@ -96,9 +96,9 @@ public class TextEditorActivity extends ThemedActivity private static final String KEY_MONOFONT = "monofont"; private RelativeLayout searchViewLayout; - public ImageButton upButton; - public ImageButton downButton; - public ImageButton closeButton; + public AppCompatImageButton upButton; + public AppCompatImageButton downButton; + public AppCompatImageButton closeButton; private Snackbar loadingSnackbar; diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java index 18aa5742b3..977ba264e4 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/ColorPickerDialog.java @@ -43,10 +43,10 @@ import android.view.View; import android.widget.LinearLayout; import android.widget.RadioButton; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import androidx.appcompat.widget.AppCompatTextView; import androidx.core.util.Pair; import androidx.preference.Preference.BaseSavedState; import androidx.preference.PreferenceDialogFragmentCompat; @@ -167,7 +167,7 @@ public void onBindDialogView(View view) { select(selectedItem, true); } - ((TextView) child.findViewById(R.id.text)).setText(COLORS[i].first); + ((AppCompatTextView) child.findViewById(R.id.text)).setText(COLORS[i].first); CircularColorsView colorsView = child.findViewById(R.id.circularColorsView); colorsView.setColors(getColor(i, 0), getColor(i, 1), getColor(i, 2), getColor(i, 3)); AppTheme appTheme = @@ -185,7 +185,7 @@ public void onBindDialogView(View view) { select(selectedItem, true); } - ((TextView) child.findViewById(R.id.text)).setText(R.string.custom); + ((AppCompatTextView) child.findViewById(R.id.text)).setText(R.string.custom); child.findViewById(R.id.circularColorsView).setVisibility(View.INVISIBLE); container.addView(child); } @@ -197,7 +197,7 @@ public void onBindDialogView(View view) { select(selectedItem, true); } - ((TextView) child.findViewById(R.id.text)).setText(R.string.random); + ((AppCompatTextView) child.findViewById(R.id.text)).setText(R.string.random); child.findViewById(R.id.circularColorsView).setVisibility(View.INVISIBLE); container.addView(child); } @@ -249,9 +249,9 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { ((UserColorPreferences) requireArguments().getParcelable(ARG_COLOR_PREF)).getAccent(); // Button views - ((TextView) dialog.findViewById(res.getIdentifier("button1", "id", "android"))) + ((AppCompatTextView) dialog.findViewById(res.getIdentifier("button1", "id", "android"))) .setTextColor(accentColor); - ((TextView) dialog.findViewById(res.getIdentifier("button2", "id", "android"))) + ((AppCompatTextView) dialog.findViewById(res.getIdentifier("button2", "id", "android"))) .setTextColor(accentColor); return dialog; diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt index 149a24a319..5647c930de 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/DecryptFingerprintDialog.kt @@ -25,8 +25,8 @@ import android.content.Intent import android.hardware.fingerprint.FingerprintManager import android.os.Build import android.view.View -import android.widget.Button import androidx.annotation.RequiresApi +import androidx.appcompat.widget.AppCompatButton import com.afollestad.materialdialogs.MaterialDialog import com.amaze.filemanager.R import com.amaze.filemanager.filesystem.files.CryptUtil @@ -62,7 +62,9 @@ object DecryptFingerprintDialog { val builder = MaterialDialog.Builder(c) builder.title(c.getString(R.string.crypt_decrypt)) val rootView = View.inflate(c, R.layout.dialog_decrypt_fingerprint_authentication, null) - val cancelButton = rootView.findViewById