├── settings.gradle ├── .gitignore ├── app ├── debug.keystore ├── src │ └── main │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── xml │ │ │ └── provider_paths.xml │ │ ├── anim │ │ │ ├── fadein.xml │ │ │ ├── exit_to_right.xml │ │ │ └── enter_from_right.xml │ │ ├── drawable │ │ │ ├── toolbar_shadow.xml │ │ │ ├── extension_border.xml │ │ │ ├── ic_plus.xml │ │ │ ├── ic_delete.xml │ │ │ ├── ic_folder.xml │ │ │ ├── ic_file.xml │ │ │ ├── ic_select.xml │ │ │ ├── ic_share.xml │ │ │ ├── ic_paste.xml │ │ │ ├── ic_sd_card.xml │ │ │ ├── ic_cut.xml │ │ │ ├── ic_rename.xml │ │ │ ├── ic_audio.xml │ │ │ ├── ic_copy.xml │ │ │ ├── ic_pdf.xml │ │ │ └── ic_video.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-v21 │ │ │ └── styles.xml │ │ ├── layout │ │ │ ├── screen_storage.xml │ │ │ ├── dialog_rename.xml │ │ │ ├── toolbar.xml │ │ │ ├── screen_main.xml │ │ │ ├── screen_folder.xml │ │ │ ├── row_storage.xml │ │ │ ├── row_file.xml │ │ │ └── button_bar.xml │ │ ├── values-pt │ │ │ └── strings.xml │ │ ├── values-es │ │ │ └── strings.xml │ │ ├── values-tr │ │ │ └── strings.xml │ │ ├── values-el │ │ │ └── strings.xml │ │ ├── values-de │ │ │ └── strings.xml │ │ └── values-fr │ │ │ └── strings.xml │ │ ├── java │ │ └── com │ │ │ └── mauriciotogneri │ │ │ └── fileexplorer │ │ │ ├── utils │ │ │ ├── CrashUtils.java │ │ │ ├── SpaceFormatter.java │ │ │ ├── ThumbnailLoader.java │ │ │ └── Dialogs.java │ │ │ ├── provider │ │ │ ├── LegacyCompatFileProvider.java │ │ │ └── LegacyCompatCursorWrapper.java │ │ │ ├── models │ │ │ ├── ToolBar.java │ │ │ ├── Clipboard.java │ │ │ ├── ButtonBar.java │ │ │ └── FileInfo.java │ │ │ ├── app │ │ │ ├── FileExplorer.java │ │ │ └── MainActivity.java │ │ │ ├── base │ │ │ └── BaseListAdapter.java │ │ │ ├── adapters │ │ │ ├── StorageAdapter.java │ │ │ └── FolderAdapter.java │ │ │ └── fragments │ │ │ ├── StorageFragment.java │ │ │ └── FolderFragment.java │ │ └── AndroidManifest.xml ├── google-services.json └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── LICENSE.md ├── gradlew.bat ├── privacy_policy.txt └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | gradle.properties 3 | .gradle 4 | /.idea 5 | /local.properties 6 | /app/build 7 | /build -------------------------------------------------------------------------------- /app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/app/debug.keystore -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Play-Store-Mauricio-Togneri/fileexplorer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 26 15:11:39 CET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fadein.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/utils/CrashUtils.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.utils; 2 | 3 | import com.google.firebase.crashlytics.FirebaseCrashlytics; 4 | 5 | public class CrashUtils 6 | { 7 | public static void report(Throwable t) 8 | { 9 | FirebaseCrashlytics.getInstance().recordException(t); 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/exit_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/toolbar_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/enter_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #666666 5 | #AAAAAA 6 | #CCCCCC 7 | #DDDDDD 8 | 9 | #0075A8 10 | #0081BA 11 | 12 | #FFFFFF 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/extension_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_plus.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/screen_storage.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/provider/LegacyCompatFileProvider.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.provider; 2 | 3 | import android.database.Cursor; 4 | import android.net.Uri; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.core.content.FileProvider; 8 | 9 | public class LegacyCompatFileProvider extends FileProvider 10 | { 11 | @Override 12 | public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 13 | { 14 | return (new LegacyCompatCursorWrapper(super.query(uri, projection, selection, selectionArgs, sortOrder))); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_folder.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_rename.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File Explorer 2 | 3 | A simple file explorer with basic functionalities for those who just aim to browse their files. 4 | 5 | ## Configuration 6 | 7 | After you clone the repository you have to add the following lines to the file `gradle.properties`: 8 | 9 | ```ini 10 | RELEASE_KEY_ALIAS = ??? 11 | RELEASE_KEY_PASSWORD = ??? 12 | RELEASE_STORE_PASSWORD = ??? 13 | RELEASE_STORE_FILE = ??? 14 | ``` 15 | 16 | 17 | 18 | 19 | 20 | ## TODO 21 | * Navigation drawer 22 | * Smaller folder path in title (ellipse start) 23 | * If no app to open file, let user select among all apps 24 | * Search (global and current folder) 25 | * Sort by (alphabetically/size) 26 | * Change view (list or tiles - small, medium or big) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/utils/SpaceFormatter.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.utils; 2 | 3 | import java.util.Locale; 4 | 5 | public class SpaceFormatter 6 | { 7 | public String format(long originalSize) 8 | { 9 | String label = "B"; 10 | double size = originalSize; 11 | 12 | if (size > 1024) 13 | { 14 | size /= 1024; 15 | label = "KB"; 16 | } 17 | 18 | if (size > 1024) 19 | { 20 | size /= 1024; 21 | label = "MB"; 22 | } 23 | 24 | if (size > 1024) 25 | { 26 | size /= 1024; 27 | label = "GB"; 28 | } 29 | 30 | if (size % 1 == 0) 31 | { 32 | return String.format(Locale.getDefault(), "%d %s", (long) size, label); 33 | } 34 | else 35 | { 36 | return String.format(Locale.getDefault(), "%.1f %s", size, label); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_paste.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/models/ToolBar.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.models; 2 | 3 | import android.widget.TextView; 4 | 5 | import com.mauriciotogneri.fileexplorer.R; 6 | import com.mauriciotogneri.fileexplorer.fragments.FolderFragment; 7 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 8 | 9 | public class ToolBar 10 | { 11 | private final TextView folderName; 12 | 13 | public ToolBar(TextView textview) 14 | { 15 | this.folderName = textview; 16 | } 17 | 18 | public void update(FolderFragment fragment) 19 | { 20 | updateTitle(fragment.folderName()); 21 | } 22 | 23 | public void update(String title) 24 | { 25 | updateTitle(title); 26 | } 27 | 28 | private void updateTitle(String text) 29 | { 30 | try 31 | { 32 | folderName.setText(text); 33 | } 34 | catch (Exception e) 35 | { 36 | CrashUtils.report(e); 37 | 38 | folderName.setText(R.string.app_name); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sd_card.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Mauricio Togneri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /app/src/main/res/layout/screen_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 11 | 14 | 15 | 18 | 19 | 23 | 24 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/screen_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 20 | 21 | 22 | 23 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Delete 7 | Rename 8 | Create 9 | Cancel 10 | 11 | No items 12 | 13 | 14 | %d items 15 | 1 item 16 | %d items 17 | 18 | 19 | Total 20 | Available 21 | 22 | Error renaming 23 | 24 | Cannot open file 25 | 26 | Moving… 27 | Copying… 28 | 29 | Error creating folder 30 | 31 | Delete selected items? 32 | Deleting… 33 | Error deleting 34 | 35 | Share file 36 | Error sharing file 37 | Error sharing files 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cut.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_rename.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Eliminar 7 | Mudar nome 8 | Criar 9 | Cancelar 10 | 11 | Sem conteúdo 12 | 13 | 14 | %d itens 15 | 1 item 16 | %d itens 17 | 18 | 19 | Total 20 | Disponível 21 | 22 | Erro ao mudar nome 23 | 24 | Impossível abrir ficheiro 25 | 26 | Movendo… 27 | Copiando… 28 | 29 | Erro ao criar pasta 30 | 31 | Eliminar itens seleccionados? 32 | Eliminando… 33 | Erro ao eliminar 34 | 35 | Partilhar ficheiro 36 | Erro ao partilhar ficheiro 37 | Error ao partilhar ficheiros 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_audio.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Eliminar 7 | Renombrar 8 | Crear 9 | Cancelar 10 | 11 | Carpeta vacía 12 | 13 | 14 | %d elementos 15 | 1 elemento 16 | %d elementos 17 | 18 | 19 | Total 20 | Disponible 21 | 22 | Error renombrando 23 | 24 | No se puede abrir el fichero 25 | 26 | Moviendo… 27 | Copiando… 28 | 29 | Error creando la carpeta 30 | 31 | ¿Eliminar los elementos seleccionados? 32 | Eliminando… 33 | Error eliminando 34 | 35 | Compartir fichero 36 | Error compartiendo fichero 37 | Error compartiendo ficheros 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Sil 7 | Yeniden adlandır 8 | Oluştur 9 | İptal 10 | 11 | Öğe yok 12 | 13 | 14 | %d öğe 15 | 1 öğe 16 | %d öğe 17 | 18 | 19 | Toplam 20 | Mevcut 21 | 22 | Yeniden adlandırırken hata oluştu 23 | 24 | Dosya açılamıyor 25 | 26 | Taşınıyor… 27 | Kopyalanıyor… 28 | 29 | Klasör oluşturulurken hata oluştu 30 | 31 | Seçtiğiniz öğeler silinsin mi? 32 | Siliniyor… 33 | Öğeler silinirken hata oluştu 34 | 35 | Dosyayı paylaş 36 | Dosya paylaşılırken hata oluştu 37 | Dosyalar paylaşılırken hata oluştu 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/values-el/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Διαγραφή 7 | Μετονομασία 8 | Δημιουργία 9 | Ακύρωση 10 | 11 | Κανένα αντικείμενο 12 | 13 | 14 | %d αντικείμενα 15 | 1 αντικείμενο 16 | %d αντικείμενα 17 | 18 | 19 | Συνολικά 20 | Διαθέσιμα 21 | 22 | Σφάλμα μετονομασίας 23 | 24 | Αδυναμία ανοίγματος αρχείου 25 | 26 | Μεταφορά… 27 | Αντιγραφή… 28 | 29 | Αδυναμία δημιουργίας φακέλου 30 | 31 | Διαγραφή επιλεγμένων αντικειμένων; 32 | Διαγραφή… 33 | Σφάλμα διαγραφής 34 | 35 | Κοινή χρήση αρχείου 36 | Σφάλμα κοινής χρήσης αρχείου 37 | Σφάλμα κοινής χρήσης αρχείων 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | 26 | 27 | 31 | 32 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/row_storage.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 22 | 23 | 31 | 32 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Löschen 7 | Umbenennen 8 | Erstellen 9 | Abbrechen 10 | 11 | Keine Elemente 12 | 13 | 14 | %d Elemente 15 | 1 Element 16 | %d Elemente 17 | 18 | 19 | Gesamt 20 | Verfügbar 21 | 22 | Fehler beim Umbenennen 23 | 24 | Datei kann nicht geöffnet werden 25 | 26 | Verschieben… 27 | Kopieren… 28 | 29 | Fehler beim Erstellen des Ordners 30 | 31 | Sollen die ausgewählten Elemente wirklich gelöscht werden? 32 | Löschen… 33 | Fehler beim Löschen 34 | 35 | Datei freigeben 36 | Fehler beim Freigeben der Datei 37 | Fehler beim Freigeben der Dateien 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | File Explorer 5 | 6 | Supprimer 7 | Renommer 8 | Créer 9 | Annuler 10 | 11 | Dossier vide 12 | 13 | 14 | %d élément 15 | 1 élément 16 | %d éléments 17 | 18 | 19 | Total 20 | Disponible 21 | 22 | Erreur lors du renommage 23 | 24 | Impossible d\'ouvir le fichier 25 | 26 | Déplacement… 27 | Copie… 28 | 29 | Erreur lors de la création du dossier 30 | 31 | Voulez vous supprimer les éléments séléctionnés? 32 | Supression… 33 | Erreur lors de la supression 34 | 35 | Partager le fichier 36 | Erreur lors du partage du fichier 37 | Erreur lors du partage des fichiers 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/utils/ThumbnailLoader.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.utils; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Bitmap; 5 | import android.util.TypedValue; 6 | import android.view.animation.Animation; 7 | import android.view.animation.AnimationUtils; 8 | import android.widget.ImageView; 9 | 10 | import com.mauriciotogneri.fileexplorer.R; 11 | import com.mauriciotogneri.fileexplorer.models.FileInfo; 12 | 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | 16 | public class ThumbnailLoader 17 | { 18 | private final int maxSize; 19 | private final ExecutorService threadPool; 20 | private static final int MAX_DP = 24; 21 | 22 | public ThumbnailLoader(Resources resources) 23 | { 24 | this.maxSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, MAX_DP, resources.getDisplayMetrics()); 25 | this.threadPool = Executors.newFixedThreadPool(10); 26 | } 27 | 28 | public void load(FileInfo fileInfo, ImageView imageView) 29 | { 30 | if (fileInfo.hasCachedBitmap()) 31 | { 32 | imageView.setImageBitmap(fileInfo.bitmap(maxSize)); 33 | } 34 | else 35 | { 36 | threadPool.submit(() -> { 37 | Bitmap bitmap = fileInfo.bitmap(maxSize); 38 | 39 | imageView.post(() -> { 40 | imageView.setImageBitmap(bitmap); 41 | 42 | Animation animation = AnimationUtils.loadAnimation(imageView.getContext(), R.anim.fadein); 43 | imageView.startAnimation(animation); 44 | }); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "916031414808", 4 | "firebase_url": "https://file-explorer-4fc1d.firebaseio.com", 5 | "project_id": "file-explorer-4fc1d", 6 | "storage_bucket": "file-explorer-4fc1d.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:916031414808:android:348ac6261153e535", 12 | "android_client_info": { 13 | "package_name": "com.mauriciotogneri.fileexplorer" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "916031414808-3u1sri8pgfh63k8cegojrqvj52u08aj1.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.mauriciotogneri.fileexplorer", 22 | "certificate_hash": "808DCD3D2690FF6F4D7BF006A9693003DB48BC66" 23 | } 24 | }, 25 | { 26 | "client_id": "916031414808-7bqdqs8695tkqais7oup1296eevpsa4h.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyAjxsssaLayH5EdE7MZ5nA0Cxzh1DTa4jE" 33 | } 34 | ], 35 | "services": { 36 | "analytics_service": { 37 | "status": 1 38 | }, 39 | "appinvite_service": { 40 | "status": 2, 41 | "other_platform_oauth_client": [ 42 | { 43 | "client_id": "916031414808-7bqdqs8695tkqais7oup1296eevpsa4h.apps.googleusercontent.com", 44 | "client_type": 3 45 | } 46 | ] 47 | }, 48 | "ads_service": { 49 | "status": 2 50 | } 51 | } 52 | } 53 | ], 54 | "configuration_version": "1" 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/app/FileExplorer.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.app; 2 | 3 | import android.os.StrictMode; 4 | 5 | import com.mauriciotogneri.fileexplorer.BuildConfig; 6 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 7 | 8 | import java.lang.Thread.UncaughtExceptionHandler; 9 | 10 | import androidx.multidex.MultiDexApplication; 11 | 12 | public class FileExplorer extends MultiDexApplication 13 | { 14 | @Override 15 | public void onCreate() 16 | { 17 | super.onCreate(); 18 | 19 | Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler()); 20 | 21 | if (BuildConfig.DEBUG) 22 | { 23 | StrictMode.ThreadPolicy.Builder threadBuilder = new StrictMode.ThreadPolicy.Builder(); 24 | threadBuilder.detectAll(); 25 | threadBuilder.penaltyLog(); 26 | StrictMode.setThreadPolicy(threadBuilder.build()); 27 | 28 | StrictMode.VmPolicy.Builder vmBuilder = new StrictMode.VmPolicy.Builder(); 29 | vmBuilder.detectAll(); 30 | vmBuilder.penaltyLog(); 31 | StrictMode.setVmPolicy(vmBuilder.build()); 32 | } 33 | } 34 | 35 | public class CustomExceptionHandler implements UncaughtExceptionHandler 36 | { 37 | private final UncaughtExceptionHandler defaultHandler; 38 | 39 | public CustomExceptionHandler() 40 | { 41 | this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 42 | } 43 | 44 | @Override 45 | public void uncaughtException(Thread thread, Throwable throwable) 46 | { 47 | CrashUtils.report(throwable); 48 | 49 | defaultHandler.uncaughtException(thread, throwable); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/base/BaseListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.base; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import androidx.annotation.NonNull; 13 | 14 | public abstract class BaseListAdapter extends ArrayAdapter 15 | { 16 | private final int resourceId; 17 | 18 | protected BaseListAdapter(Context context, int resourceId) 19 | { 20 | super(context, resourceId, new ArrayList<>()); 21 | 22 | this.resourceId = resourceId; 23 | } 24 | 25 | protected abstract V viewHolder(View view); 26 | 27 | protected abstract void fillView(View rowView, V viewHolder, T item); 28 | 29 | public void update(List list) 30 | { 31 | clear(); 32 | addAll(list); 33 | notifyDataSetChanged(); 34 | } 35 | 36 | @Override 37 | @NonNull 38 | @SuppressWarnings("unchecked") 39 | public View getView(int position, View convertView, @NonNull ViewGroup parent) 40 | { 41 | V viewHolder; 42 | 43 | View rowView = convertView; 44 | 45 | if (rowView == null) 46 | { 47 | LayoutInflater inflater = LayoutInflater.from(getContext()); 48 | rowView = inflater.inflate(resourceId, parent, false); 49 | 50 | viewHolder = viewHolder(rowView); 51 | rowView.setTag(viewHolder); 52 | } 53 | else 54 | { 55 | viewHolder = (V) rowView.getTag(); 56 | } 57 | 58 | T item = getItem(position); 59 | 60 | fillView(rowView, viewHolder, item); 61 | 62 | return rowView; 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pdf.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/adapters/StorageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.adapters; 2 | 3 | import android.content.Context; 4 | import android.os.StatFs; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.mauriciotogneri.fileexplorer.R; 9 | import com.mauriciotogneri.fileexplorer.adapters.StorageAdapter.ViewHolder; 10 | import com.mauriciotogneri.fileexplorer.base.BaseListAdapter; 11 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 12 | import com.mauriciotogneri.fileexplorer.utils.SpaceFormatter; 13 | 14 | public class StorageAdapter extends BaseListAdapter 15 | { 16 | public StorageAdapter(Context context) 17 | { 18 | super(context, R.layout.row_storage); 19 | } 20 | 21 | @Override 22 | protected ViewHolder viewHolder(View view) 23 | { 24 | return new ViewHolder(view); 25 | } 26 | 27 | @Override 28 | protected void fillView(View rowView, ViewHolder viewHolder, String item) 29 | { 30 | viewHolder.name.setText(item); 31 | 32 | try 33 | { 34 | StatFs stat = new StatFs(item); 35 | long blockSize = (long) stat.getBlockSize(); 36 | long totalSpace = blockSize * stat.getBlockCount(); 37 | long availableSpace = blockSize * stat.getAvailableBlocks(); 38 | 39 | SpaceFormatter spaceFormatter = new SpaceFormatter(); 40 | 41 | String labelTotal = getContext().getString(R.string.space_total); 42 | String total = spaceFormatter.format(totalSpace); 43 | String labelAvailable = getContext().getString(R.string.space_available); 44 | String available = spaceFormatter.format(availableSpace); 45 | viewHolder.space.setText(String.format("%s: %s %s: %s", labelTotal, total, labelAvailable, available)); 46 | } 47 | catch (Exception e) 48 | { 49 | CrashUtils.report(e); 50 | } 51 | } 52 | 53 | protected static class ViewHolder 54 | { 55 | public final TextView name; 56 | public final TextView space; 57 | 58 | public ViewHolder(View view) 59 | { 60 | this.name = view.findViewById(R.id.name); 61 | this.space = view.findViewById(R.id.space); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android 4 | { 5 | compileSdkVersion 29 6 | buildToolsVersion '29.0.3' 7 | 8 | defaultConfig 9 | { 10 | applicationId 'com.mauriciotogneri.fileexplorer' 11 | minSdkVersion 16 12 | targetSdkVersion 28 13 | versionCode 18 14 | versionName '1.5.0' 15 | multiDexEnabled true 16 | } 17 | 18 | signingConfigs 19 | { 20 | debug 21 | { 22 | storeFile file('debug.keystore') 23 | } 24 | 25 | release 26 | { 27 | keyAlias project.hasProperty('RELEASE_KEY_ALIAS') ? RELEASE_KEY_ALIAS : '' 28 | keyPassword project.hasProperty('RELEASE_KEY_PASSWORD') ? RELEASE_KEY_PASSWORD : '' 29 | storePassword project.hasProperty('RELEASE_STORE_PASSWORD') ? RELEASE_STORE_PASSWORD : '' 30 | storeFile project.hasProperty('RELEASE_STORE_FILE') ? file(RELEASE_STORE_FILE) : file('.') 31 | } 32 | } 33 | 34 | dexOptions 35 | { 36 | javaMaxHeapSize '2g' 37 | preDexLibraries true 38 | } 39 | 40 | lintOptions 41 | { 42 | checkReleaseBuilds false 43 | abortOnError false 44 | } 45 | 46 | compileOptions 47 | { 48 | sourceCompatibility JavaVersion.VERSION_1_8 49 | targetCompatibility JavaVersion.VERSION_1_8 50 | } 51 | 52 | buildTypes 53 | { 54 | debug 55 | { 56 | debuggable true 57 | } 58 | 59 | release 60 | { 61 | minifyEnabled false 62 | signingConfig signingConfigs.release 63 | } 64 | } 65 | } 66 | 67 | dependencies 68 | { 69 | implementation 'androidx.multidex:multidex:2.0.1' 70 | 71 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 72 | implementation 'androidx.appcompat:appcompat:1.1.0' 73 | implementation 'com.google.android.material:material:1.2.0-alpha05' 74 | 75 | // firebase 76 | implementation 'com.google.firebase:firebase-core:17.2.2' 77 | implementation 'com.google.firebase:firebase-analytics:17.2.2' 78 | implementation 'com.google.firebase:firebase-messaging:20.1.0' 79 | implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta01' 80 | implementation 'com.google.firebase:firebase-inappmessaging-display:19.0.3' 81 | } 82 | 83 | apply plugin: 'com.google.gms.google-services' 84 | apply plugin: 'com.google.firebase.crashlytics' -------------------------------------------------------------------------------- /app/src/main/res/layout/row_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 21 | 22 | 37 | 38 | 39 | 40 | 44 | 45 | 53 | 54 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | 51 | 52 | 53 | 54 | 57 | 58 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/button_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/models/Clipboard.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.models; 2 | 3 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class Clipboard 10 | { 11 | private File parent = null; 12 | private Mode mode = Mode.NONE; 13 | private List items = new ArrayList<>(); 14 | 15 | private enum Mode 16 | { 17 | NONE, 18 | CUT, 19 | COPY 20 | } 21 | 22 | public Clipboard() 23 | { 24 | } 25 | 26 | public boolean isCut() 27 | { 28 | return mode == Mode.CUT; 29 | } 30 | 31 | public boolean isCopy() 32 | { 33 | return mode == Mode.COPY; 34 | } 35 | 36 | public void cut(List items) 37 | { 38 | if (!items.isEmpty()) 39 | { 40 | parent = items.get(0).parent(); 41 | } 42 | 43 | mode = Mode.CUT; 44 | this.items = items; 45 | } 46 | 47 | public void copy(List items) 48 | { 49 | if (!items.isEmpty()) 50 | { 51 | parent = items.get(0).parent(); 52 | } 53 | 54 | mode = Mode.COPY; 55 | this.items = items; 56 | } 57 | 58 | public void paste(FileInfo target) 59 | { 60 | try 61 | { 62 | for (FileInfo fileInfo : items) 63 | { 64 | fileInfo.copy(target, mode == Mode.CUT); 65 | } 66 | } 67 | catch (Exception e) 68 | { 69 | CrashUtils.report(e); 70 | } 71 | 72 | items.clear(); 73 | mode = Mode.NONE; 74 | parent = null; 75 | } 76 | 77 | public boolean someExist() 78 | { 79 | for (FileInfo fileInfo : items) 80 | { 81 | if (fileInfo.exists()) 82 | { 83 | return true; 84 | } 85 | } 86 | 87 | return false; 88 | } 89 | 90 | public boolean isEmpty() 91 | { 92 | return items.isEmpty(); 93 | } 94 | 95 | public boolean hasParent(File target) 96 | { 97 | for (FileInfo fileInfo : items) 98 | { 99 | if (target.getAbsolutePath().startsWith(fileInfo.path())) 100 | { 101 | return true; 102 | } 103 | } 104 | 105 | return (parent.compareTo(target) == 0); 106 | } 107 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_video.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/fragments/StorageFragment.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ListView; 9 | 10 | import com.mauriciotogneri.fileexplorer.R; 11 | import com.mauriciotogneri.fileexplorer.adapters.StorageAdapter; 12 | import com.mauriciotogneri.fileexplorer.app.MainActivity; 13 | 14 | import java.util.Arrays; 15 | 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class StorageFragment extends Fragment 19 | { 20 | private static final String PARAMETER_STORAGES_PATH = "storages.path"; 21 | 22 | private MainActivity mainActivity; 23 | private ListView listView; 24 | private StorageAdapter adapter; 25 | 26 | public static StorageFragment newInstance(String[] storagesPath) 27 | { 28 | StorageFragment fragment = new StorageFragment(); 29 | Bundle parameters = new Bundle(); 30 | parameters.putStringArray(PARAMETER_STORAGES_PATH, storagesPath); 31 | fragment.setArguments(parameters); 32 | 33 | return fragment; 34 | } 35 | 36 | @Override 37 | public void onAttach(Context context) 38 | { 39 | super.onAttach(context); 40 | 41 | mainActivity = (MainActivity) context; 42 | } 43 | 44 | @Override 45 | public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 46 | { 47 | View view = inflater.inflate(R.layout.screen_storage, container, false); 48 | 49 | listView = view.findViewById(R.id.list); 50 | 51 | return view; 52 | } 53 | 54 | @Override 55 | public final void onActivityCreated(Bundle savedInstanceState) 56 | { 57 | super.onActivityCreated(savedInstanceState); 58 | 59 | adapter = new StorageAdapter(mainActivity); 60 | 61 | listView.setAdapter(adapter); 62 | listView.setOnItemClickListener((parent, view, position, id) -> { 63 | String storagePath = (String) parent.getItemAtPosition(position); 64 | openStorage(storagePath); 65 | }); 66 | 67 | reload(); 68 | } 69 | 70 | public void reload() 71 | { 72 | adapter.update(Arrays.asList(storages())); 73 | } 74 | 75 | private String[] storages() 76 | { 77 | Bundle extras = getArguments(); 78 | 79 | return (extras != null) ? extras.getStringArray(PARAMETER_STORAGES_PATH) : new String[0]; 80 | } 81 | 82 | private void openStorage(String storagePath) 83 | { 84 | FolderFragment folderFragment = FolderFragment.newInstance(storagePath); 85 | 86 | mainActivity.addFragment(folderFragment, true); 87 | } 88 | 89 | @Override 90 | public void onSaveInstanceState(Bundle outState) 91 | { 92 | // no call for super(). Bug on API Level > 11. 93 | } 94 | } -------------------------------------------------------------------------------- /privacy_policy.txt: -------------------------------------------------------------------------------- 1 | Privacy Policy 2 | The developer built the File Explorer app as a Free app. This SERVICE is provided by The developer at no cost and is intended for use as is. 3 | 4 | This page is used to inform website visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service. 5 | 6 | If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy. 7 | 8 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at File Explorer unless otherwise defined in this Privacy Policy. 9 | 10 | Information Collection and Use 11 | 12 | For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request is retained on your device and is not collected by me in any way 13 | 14 | The app does use third party services that may collect information used to identify you. 15 | 16 | Log Data 17 | 18 | I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics. 19 | 20 | Cookies 21 | 22 | Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory. 23 | 24 | This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service. 25 | 26 | Service Providers 27 | 28 | I may employ third-party companies and individuals due to the following reasons: 29 | 30 | To facilitate our Service; 31 | To provide the Service on our behalf; 32 | To perform Service-related services; or 33 | To assist us in analyzing how our Service is used. 34 | I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose. 35 | 36 | Security 37 | 38 | I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security. 39 | 40 | Links to Other Sites 41 | 42 | This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services. 43 | 44 | Children’s Privacy 45 | 46 | These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions. 47 | 48 | Changes to This Privacy Policy 49 | 50 | I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page. 51 | 52 | Contact Us 53 | 54 | If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me. 55 | 56 | This privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/provider/LegacyCompatCursorWrapper.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.provider; 2 | 3 | import android.database.Cursor; 4 | import android.database.CursorWrapper; 5 | import android.net.Uri; 6 | 7 | import java.util.Arrays; 8 | 9 | import static android.provider.MediaStore.MediaColumns.DATA; 10 | import static android.provider.MediaStore.MediaColumns.MIME_TYPE; 11 | 12 | /** 13 | * Wraps the Cursor returned by an ordinary FileProvider, 14 | * StreamProvider, or other ContentProvider. If the query() 15 | * requests _DATA or MIME_TYPE, adds in some values for 16 | * that column, so the client getting this Cursor is less 17 | * likely to crash. Of course, clients should not be requesting 18 | * either of these columns in the first place... 19 | */ 20 | public class LegacyCompatCursorWrapper extends CursorWrapper 21 | { 22 | final private int fakeDataColumn; 23 | final private int fakeMimeTypeColumn; 24 | final private String mimeType; 25 | final private Uri uriForDataColumn; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param cursor the Cursor to be wrapped 31 | */ 32 | public LegacyCompatCursorWrapper(Cursor cursor) 33 | { 34 | this(cursor, null); 35 | } 36 | 37 | /** 38 | * Constructor. 39 | * 40 | * @param cursor the Cursor to be wrapped 41 | * @param mimeType the MIME type of the content represented 42 | * by the Uri that generated this Cursor, should 43 | * we need it 44 | */ 45 | public LegacyCompatCursorWrapper(Cursor cursor, String mimeType) 46 | { 47 | this(cursor, mimeType, null); 48 | } 49 | 50 | /** 51 | * Constructor. 52 | * 53 | * @param cursor the Cursor to be wrapped 54 | * @param mimeType the MIME type of the content represented 55 | * by the Uri that generated this Cursor, should 56 | * we need it 57 | * @param uriForDataColumn Uri to return for the _DATA column 58 | */ 59 | public LegacyCompatCursorWrapper(Cursor cursor, String mimeType, 60 | Uri uriForDataColumn) 61 | { 62 | super(cursor); 63 | 64 | this.uriForDataColumn = uriForDataColumn; 65 | 66 | if (cursor.getColumnIndex(DATA) >= 0) 67 | { 68 | fakeDataColumn = -1; 69 | } 70 | else 71 | { 72 | fakeDataColumn = cursor.getColumnCount(); 73 | } 74 | 75 | if (cursor.getColumnIndex(MIME_TYPE) >= 0) 76 | { 77 | fakeMimeTypeColumn = -1; 78 | } 79 | else if (fakeDataColumn == -1) 80 | { 81 | fakeMimeTypeColumn = cursor.getColumnCount(); 82 | } 83 | else 84 | { 85 | fakeMimeTypeColumn = fakeDataColumn + 1; 86 | } 87 | 88 | this.mimeType = mimeType; 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | */ 94 | @Override 95 | public int getColumnCount() 96 | { 97 | int count = super.getColumnCount(); 98 | 99 | if (!cursorHasDataColumn()) 100 | { 101 | count += 1; 102 | } 103 | 104 | if (!cursorHasMimeTypeColumn()) 105 | { 106 | count += 1; 107 | } 108 | 109 | return (count); 110 | } 111 | 112 | /** 113 | * {@inheritDoc} 114 | */ 115 | @Override 116 | public int getColumnIndex(String columnName) 117 | { 118 | if (!cursorHasDataColumn() && DATA.equalsIgnoreCase( 119 | columnName)) 120 | { 121 | return (fakeDataColumn); 122 | } 123 | 124 | if (!cursorHasMimeTypeColumn() && MIME_TYPE.equalsIgnoreCase( 125 | columnName)) 126 | { 127 | return (fakeMimeTypeColumn); 128 | } 129 | 130 | return (super.getColumnIndex(columnName)); 131 | } 132 | 133 | /** 134 | * {@inheritDoc} 135 | */ 136 | @Override 137 | public String getColumnName(int columnIndex) 138 | { 139 | if (columnIndex == fakeDataColumn) 140 | { 141 | return (DATA); 142 | } 143 | 144 | if (columnIndex == fakeMimeTypeColumn) 145 | { 146 | return (MIME_TYPE); 147 | } 148 | 149 | return (super.getColumnName(columnIndex)); 150 | } 151 | 152 | /** 153 | * {@inheritDoc} 154 | */ 155 | @Override 156 | public String[] getColumnNames() 157 | { 158 | if (cursorHasDataColumn() && cursorHasMimeTypeColumn()) 159 | { 160 | return (super.getColumnNames()); 161 | } 162 | 163 | String[] orig = super.getColumnNames(); 164 | String[] result = Arrays.copyOf(orig, getColumnCount()); 165 | 166 | if (!cursorHasDataColumn()) 167 | { 168 | result[fakeDataColumn] = DATA; 169 | } 170 | 171 | if (!cursorHasMimeTypeColumn()) 172 | { 173 | result[fakeMimeTypeColumn] = MIME_TYPE; 174 | } 175 | 176 | return (result); 177 | } 178 | 179 | /** 180 | * {@inheritDoc} 181 | */ 182 | @Override 183 | public String getString(int columnIndex) 184 | { 185 | if (!cursorHasDataColumn() && columnIndex == fakeDataColumn) 186 | { 187 | if (uriForDataColumn != null) 188 | { 189 | return (uriForDataColumn.toString()); 190 | } 191 | 192 | return (null); 193 | } 194 | 195 | if (!cursorHasMimeTypeColumn() && columnIndex == fakeMimeTypeColumn) 196 | { 197 | return (mimeType); 198 | } 199 | 200 | return (super.getString(columnIndex)); 201 | } 202 | 203 | /** 204 | * {@inheritDoc} 205 | */ 206 | @Override 207 | public int getType(int columnIndex) 208 | { 209 | if (!cursorHasDataColumn() && columnIndex == fakeDataColumn) 210 | { 211 | return (Cursor.FIELD_TYPE_STRING); 212 | } 213 | 214 | if (!cursorHasMimeTypeColumn() && columnIndex == fakeMimeTypeColumn) 215 | { 216 | return (Cursor.FIELD_TYPE_STRING); 217 | } 218 | 219 | return (super.getType(columnIndex)); 220 | } 221 | 222 | /** 223 | * @return true if the Cursor has a _DATA column, false otherwise 224 | */ 225 | private boolean cursorHasDataColumn() 226 | { 227 | return (fakeDataColumn == -1); 228 | } 229 | 230 | /** 231 | * @return true if the Cursor has a MIME_TYPE column, false 232 | * otherwise 233 | */ 234 | private boolean cursorHasMimeTypeColumn() 235 | { 236 | return (fakeMimeTypeColumn == -1); 237 | } 238 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/utils/Dialogs.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.ProgressDialog; 5 | import android.content.Context; 6 | import android.text.Editable; 7 | import android.text.TextWatcher; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.Window; 11 | import android.view.WindowManager; 12 | import android.view.inputmethod.EditorInfo; 13 | import android.widget.EditText; 14 | 15 | import com.mauriciotogneri.fileexplorer.R; 16 | import com.mauriciotogneri.fileexplorer.adapters.FolderAdapter; 17 | import com.mauriciotogneri.fileexplorer.models.FileInfo; 18 | 19 | import java.util.List; 20 | 21 | import androidx.appcompat.app.AlertDialog; 22 | 23 | public class Dialogs 24 | { 25 | private Dialogs() 26 | { 27 | } 28 | 29 | public static ProgressDialog progress(Context context, String message) 30 | { 31 | ProgressDialog dialog = new ProgressDialog(context); 32 | dialog.setMessage(message); 33 | dialog.setCancelable(false); 34 | dialog.setCanceledOnTouchOutside(false); 35 | dialog.show(); 36 | 37 | return dialog; 38 | } 39 | 40 | @SuppressLint("InflateParams") 41 | public static void rename(Context context, FileInfo fileInfo, OnRename callback) 42 | { 43 | View view = LayoutInflater.from(context).inflate(R.layout.dialog_rename, null); 44 | EditText nameField = view.findViewById(R.id.item_name); 45 | 46 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 47 | builder.setCancelable(false); 48 | builder.setView(view); 49 | builder.setPositiveButton(R.string.dialog_rename, (dialogInterface, i) -> callback.rename(fileInfo, nameField.getText().toString())); 50 | builder.setNegativeButton(R.string.dialog_cancel, null); 51 | 52 | AlertDialog dialog = builder.create(); 53 | Window window = dialog.getWindow(); 54 | 55 | if (window != null) 56 | { 57 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 58 | } 59 | 60 | dialog.show(); 61 | 62 | String name = fileInfo.name(); 63 | 64 | nameField.setText(name); 65 | nameField.requestFocus(); 66 | 67 | int dotIndex = name.lastIndexOf("."); 68 | 69 | if (dotIndex != -1) 70 | { 71 | nameField.setSelection(0, dotIndex); 72 | } 73 | else 74 | { 75 | nameField.selectAll(); 76 | } 77 | 78 | nameField.addTextChangedListener(new TextWatcher() 79 | { 80 | @Override 81 | public void beforeTextChanged(CharSequence s, int start, int count, int after) 82 | { 83 | } 84 | 85 | @Override 86 | public void onTextChanged(CharSequence s, int start, int before, int count) 87 | { 88 | } 89 | 90 | @Override 91 | public void afterTextChanged(Editable text) 92 | { 93 | dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(text.length() != 0); 94 | } 95 | }); 96 | 97 | nameField.setOnEditorActionListener((view1, actionId, event) -> { 98 | if (actionId == EditorInfo.IME_ACTION_DONE) 99 | { 100 | try 101 | { 102 | dialog.dismiss(); 103 | } 104 | catch (Exception e) 105 | { 106 | CrashUtils.report(e); 107 | } 108 | 109 | callback.rename(fileInfo, nameField.getText().toString()); 110 | } 111 | 112 | return false; 113 | }); 114 | } 115 | 116 | public static void delete(Context context, FolderAdapter adapter, OnDelete callback) 117 | { 118 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 119 | builder.setCancelable(false); 120 | builder.setTitle(R.string.delete_confirm); 121 | builder.setPositiveButton(R.string.dialog_delete, (dialogInterface, i) -> callback.delete(adapter.selectedItems(false))); 122 | builder.setNegativeButton(R.string.dialog_cancel, null); 123 | builder.show(); 124 | } 125 | 126 | @SuppressLint("InflateParams") 127 | public static void create(Context context, OnCreate callback) 128 | { 129 | View view = LayoutInflater.from(context).inflate(R.layout.dialog_rename, null); 130 | EditText nameField = view.findViewById(R.id.item_name); 131 | 132 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 133 | builder.setCancelable(false); 134 | builder.setView(view); 135 | builder.setPositiveButton(R.string.dialog_create, (dialogInterface, i) -> callback.create(nameField.getText().toString())); 136 | builder.setNegativeButton(R.string.dialog_cancel, null); 137 | 138 | AlertDialog dialog = builder.create(); 139 | Window window = dialog.getWindow(); 140 | 141 | if (window != null) 142 | { 143 | window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 144 | } 145 | 146 | dialog.show(); 147 | 148 | dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); 149 | 150 | nameField.requestFocus(); 151 | nameField.addTextChangedListener(new TextWatcher() 152 | { 153 | @Override 154 | public void beforeTextChanged(CharSequence s, int start, int count, int after) 155 | { 156 | } 157 | 158 | @Override 159 | public void onTextChanged(CharSequence s, int start, int before, int count) 160 | { 161 | } 162 | 163 | @Override 164 | public void afterTextChanged(Editable text) 165 | { 166 | dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(text.length() != 0); 167 | } 168 | }); 169 | 170 | nameField.setOnEditorActionListener((view1, actionId, event) -> { 171 | if (actionId == EditorInfo.IME_ACTION_DONE) 172 | { 173 | try 174 | { 175 | dialog.dismiss(); 176 | } 177 | catch (Exception e) 178 | { 179 | CrashUtils.report(e); 180 | } 181 | 182 | callback.create(nameField.getText().toString()); 183 | } 184 | 185 | return false; 186 | }); 187 | } 188 | 189 | public interface OnRename 190 | { 191 | void rename(FileInfo fileInfo, String newName); 192 | } 193 | 194 | public interface OnDelete 195 | { 196 | void delete(List selectedItems); 197 | } 198 | 199 | public interface OnCreate 200 | { 201 | void create(String name); 202 | } 203 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/models/ButtonBar.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.models; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Build; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.mauriciotogneri.fileexplorer.R; 9 | import com.mauriciotogneri.fileexplorer.fragments.FolderFragment; 10 | 11 | import java.util.Stack; 12 | 13 | public class ButtonBar 14 | { 15 | private final View buttonCut; 16 | private final View buttonCopy; 17 | private final View buttonPaste; 18 | private final View buttonSelectAll; 19 | private final View buttonRename; 20 | private final View buttonShare; 21 | private final View buttonDelete; 22 | private final View buttonCreate; 23 | 24 | public ButtonBar(View parent, Stack fragments) 25 | { 26 | this.buttonCut = parent.findViewById(R.id.button_cut); 27 | this.buttonCopy = parent.findViewById(R.id.button_copy); 28 | this.buttonPaste = parent.findViewById(R.id.button_paste); 29 | this.buttonSelectAll = parent.findViewById(R.id.button_selectAll); 30 | this.buttonRename = parent.findViewById(R.id.button_rename); 31 | this.buttonShare = parent.findViewById(R.id.button_share); 32 | this.buttonDelete = parent.findViewById(R.id.button_delete); 33 | this.buttonCreate = parent.findViewById(R.id.button_create); 34 | 35 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) 36 | { 37 | fixButtonMargin(parent.getResources(), buttonCut); 38 | fixButtonMargin(parent.getResources(), buttonCopy); 39 | fixButtonMargin(parent.getResources(), buttonPaste); 40 | fixButtonMargin(parent.getResources(), buttonSelectAll); 41 | fixButtonMargin(parent.getResources(), buttonRename); 42 | fixButtonMargin(parent.getResources(), buttonShare); 43 | fixButtonMargin(parent.getResources(), buttonDelete); 44 | fixButtonMargin(parent.getResources(), buttonCreate); 45 | } 46 | 47 | this.buttonCut.setVisibility(View.GONE); 48 | this.buttonCut.setOnClickListener(view -> { 49 | if (!fragments.isEmpty()) 50 | { 51 | FolderFragment fragment = fragments.peek(); 52 | fragment.onCut(); 53 | } 54 | }); 55 | 56 | this.buttonCopy.setVisibility(View.GONE); 57 | this.buttonCopy.setOnClickListener(view -> { 58 | if (!fragments.isEmpty()) 59 | { 60 | FolderFragment fragment = fragments.peek(); 61 | fragment.onCopy(); 62 | } 63 | }); 64 | 65 | this.buttonPaste.setVisibility(View.GONE); 66 | this.buttonPaste.setOnClickListener(view -> { 67 | if (!fragments.isEmpty()) 68 | { 69 | FolderFragment fragment = fragments.peek(); 70 | fragment.onPaste(); 71 | } 72 | }); 73 | 74 | this.buttonSelectAll.setVisibility(View.GONE); 75 | this.buttonSelectAll.setOnClickListener(view -> { 76 | if (!fragments.isEmpty()) 77 | { 78 | FolderFragment fragment = fragments.peek(); 79 | fragment.onSelectAll(); 80 | } 81 | }); 82 | 83 | this.buttonRename.setVisibility(View.GONE); 84 | this.buttonRename.setOnClickListener(view -> { 85 | if (!fragments.isEmpty()) 86 | { 87 | FolderFragment fragment = fragments.peek(); 88 | fragment.onRename(); 89 | } 90 | }); 91 | 92 | this.buttonShare.setVisibility(View.GONE); 93 | this.buttonShare.setOnClickListener(view -> { 94 | if (!fragments.isEmpty()) 95 | { 96 | FolderFragment fragment = fragments.peek(); 97 | fragment.onShare(); 98 | } 99 | }); 100 | 101 | this.buttonDelete.setVisibility(View.GONE); 102 | this.buttonDelete.setOnClickListener(view -> { 103 | if (!fragments.isEmpty()) 104 | { 105 | FolderFragment fragment = fragments.peek(); 106 | fragment.onDelete(); 107 | } 108 | }); 109 | 110 | this.buttonCreate.setVisibility(View.GONE); 111 | this.buttonCreate.setOnClickListener(view -> { 112 | if (!fragments.isEmpty()) 113 | { 114 | FolderFragment fragment = fragments.peek(); 115 | fragment.onCreate(); 116 | } 117 | }); 118 | } 119 | 120 | public void displayButtons(int itemsSelected, boolean displaySelectAll, boolean displayPaste, boolean displayShare, boolean displayCreate) 121 | { 122 | if (itemsSelected > 0) 123 | { 124 | if (displaySelectAll) 125 | { 126 | buttonSelectAll.setVisibility(View.VISIBLE); 127 | } 128 | else 129 | { 130 | buttonSelectAll.setVisibility(View.GONE); 131 | } 132 | 133 | if (itemsSelected == 1) 134 | { 135 | buttonRename.setVisibility(View.VISIBLE); 136 | } 137 | else 138 | { 139 | buttonRename.setVisibility(View.GONE); 140 | } 141 | 142 | buttonCut.setVisibility(View.VISIBLE); 143 | buttonCopy.setVisibility(View.VISIBLE); 144 | buttonDelete.setVisibility(View.VISIBLE); 145 | 146 | if (displayShare) 147 | { 148 | buttonShare.setVisibility(View.VISIBLE); 149 | } 150 | } 151 | else 152 | { 153 | if (displayPaste) 154 | { 155 | buttonPaste.setVisibility(View.VISIBLE); 156 | } 157 | else 158 | { 159 | buttonPaste.setVisibility(View.GONE); 160 | } 161 | 162 | buttonCut.setVisibility(View.GONE); 163 | buttonCopy.setVisibility(View.GONE); 164 | buttonSelectAll.setVisibility(View.GONE); 165 | buttonRename.setVisibility(View.GONE); 166 | buttonShare.setVisibility(View.GONE); 167 | buttonDelete.setVisibility(View.GONE); 168 | } 169 | 170 | if (displayCreate) 171 | { 172 | if (itemsSelected > 0) 173 | { 174 | buttonCreate.setVisibility(View.GONE); 175 | } 176 | else 177 | { 178 | buttonCreate.setVisibility(View.VISIBLE); 179 | } 180 | } 181 | else 182 | { 183 | buttonCreate.setVisibility(View.GONE); 184 | } 185 | } 186 | 187 | private void fixButtonMargin(Resources resources, View view) 188 | { 189 | ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); 190 | params.setMargins(0, dpToPx(resources, -15), dpToPx(resources, -10), dpToPx(resources, -10)); 191 | view.setLayoutParams(params); 192 | } 193 | 194 | private int dpToPx(Resources resources, float dp) 195 | { 196 | float scale = resources.getDisplayMetrics().density; 197 | 198 | return (int) ((dp * scale) + 0.5f); 199 | } 200 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/adapters/FolderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.adapters; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import com.mauriciotogneri.fileexplorer.R; 10 | import com.mauriciotogneri.fileexplorer.adapters.FolderAdapter.ViewHolder; 11 | import com.mauriciotogneri.fileexplorer.base.BaseListAdapter; 12 | import com.mauriciotogneri.fileexplorer.models.FileInfo; 13 | import com.mauriciotogneri.fileexplorer.utils.ThumbnailLoader; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | import androidx.core.content.ContextCompat; 19 | 20 | public class FolderAdapter extends BaseListAdapter 21 | { 22 | private int itemsSelected = 0; 23 | private final ThumbnailLoader thumbnailLoader; 24 | 25 | public FolderAdapter(Context context) 26 | { 27 | super(context, R.layout.row_file); 28 | 29 | this.thumbnailLoader = new ThumbnailLoader(context.getResources()); 30 | } 31 | 32 | @Override 33 | protected ViewHolder viewHolder(View view) 34 | { 35 | return new ViewHolder(view); 36 | } 37 | 38 | @Override 39 | protected void fillView(View rowView, ViewHolder viewHolder, FileInfo fileInfo) 40 | { 41 | viewHolder.name.setText(fileInfo.name()); 42 | 43 | if (fileInfo.isDirectory()) 44 | { 45 | int numberOfChildren = fileInfo.numberOfChildren(); 46 | 47 | viewHolder.size.setText(getContext().getResources().getQuantityString(R.plurals.itemAmount, numberOfChildren, numberOfChildren)); 48 | 49 | viewHolder.icon.setImageResource(R.drawable.ic_folder); 50 | viewHolder.extension.setText(null); 51 | viewHolder.extension.setBackgroundResource(android.R.color.transparent); 52 | } 53 | else 54 | { 55 | viewHolder.size.setText(fileInfo.size()); 56 | 57 | if (fileInfo.isImage()) 58 | { 59 | thumbnailLoader.load(fileInfo, viewHolder.icon); 60 | viewHolder.extension.setText(null); 61 | viewHolder.extension.setBackgroundResource(android.R.color.transparent); 62 | } 63 | else if (fileInfo.isPdf()) 64 | { 65 | viewHolder.icon.setImageResource(R.drawable.ic_pdf); 66 | viewHolder.extension.setText(null); 67 | viewHolder.extension.setBackgroundResource(android.R.color.transparent); 68 | } 69 | else if (fileInfo.isAudio()) 70 | { 71 | viewHolder.icon.setImageResource(R.drawable.ic_audio); 72 | viewHolder.extension.setText(null); 73 | viewHolder.extension.setBackgroundResource(android.R.color.transparent); 74 | } 75 | else if (fileInfo.isVideo()) 76 | { 77 | viewHolder.icon.setImageResource(R.drawable.ic_video); 78 | viewHolder.extension.setText(null); 79 | viewHolder.extension.setBackgroundResource(android.R.color.transparent); 80 | } 81 | else 82 | { 83 | viewHolder.icon.setImageResource(R.drawable.ic_file); 84 | 85 | String extension = fileInfo.extension(); 86 | 87 | if (!extension.isEmpty()) 88 | { 89 | viewHolder.extension.setText(extension); 90 | viewHolder.extension.setBackgroundResource(R.drawable.extension_border); 91 | 92 | if (extension.length() <= 3) 93 | { 94 | viewHolder.extension.setTextSize(TypedValue.COMPLEX_UNIT_SP, 9); 95 | } 96 | else 97 | { 98 | viewHolder.extension.setTextSize(TypedValue.COMPLEX_UNIT_SP, 8); 99 | } 100 | } 101 | else 102 | { 103 | viewHolder.extension.setText(null); 104 | viewHolder.extension.setBackgroundResource(android.R.color.transparent); 105 | } 106 | } 107 | } 108 | 109 | if (fileInfo.isSelected()) 110 | { 111 | rowView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.gray4)); 112 | } 113 | else 114 | { 115 | rowView.setBackgroundColor(0); 116 | } 117 | } 118 | 119 | public void updateSelection(boolean itemAdded) 120 | { 121 | notifyDataSetChanged(); 122 | 123 | itemsSelected += itemAdded ? 1 : -1; 124 | } 125 | 126 | public void setData(List list) 127 | { 128 | update(list); 129 | unselectAll(); 130 | } 131 | 132 | public void unselectAll() 133 | { 134 | for (int i = 0; i < getCount(); i++) 135 | { 136 | FileInfo fileInfo = getItem(i); 137 | 138 | if (fileInfo != null) 139 | { 140 | fileInfo.select(false); 141 | } 142 | } 143 | 144 | itemsSelected = 0; 145 | notifyDataSetChanged(); 146 | } 147 | 148 | public void selectAll() 149 | { 150 | for (int i = 0; i < getCount(); i++) 151 | { 152 | FileInfo fileInfo = getItem(i); 153 | 154 | if (fileInfo != null) 155 | { 156 | fileInfo.select(true); 157 | } 158 | } 159 | 160 | itemsSelected = getCount(); 161 | notifyDataSetChanged(); 162 | } 163 | 164 | public boolean isSelectionMode() 165 | { 166 | return itemsSelected > 0; 167 | } 168 | 169 | public int itemsSelected() 170 | { 171 | return itemsSelected; 172 | } 173 | 174 | public boolean allItemsSelected() 175 | { 176 | return itemsSelected == getCount(); 177 | } 178 | 179 | public List selectedItems(boolean onlyFiles) 180 | { 181 | List list = new ArrayList<>(); 182 | 183 | for (int i = 0; i < getCount(); i++) 184 | { 185 | FileInfo fileInfo = getItem(i); 186 | 187 | if ((fileInfo != null) && fileInfo.isSelected()) 188 | { 189 | if (onlyFiles) 190 | { 191 | list.addAll(fileInfo.files()); 192 | } 193 | else 194 | { 195 | list.add(fileInfo); 196 | } 197 | } 198 | } 199 | 200 | return list; 201 | } 202 | 203 | public boolean hasFiles() 204 | { 205 | for (int i = 0; i < getCount(); i++) 206 | { 207 | FileInfo fileInfo = getItem(i); 208 | 209 | if ((fileInfo != null) && fileInfo.isSelected()) 210 | { 211 | if (fileInfo.hasFiles()) 212 | { 213 | return true; 214 | } 215 | } 216 | } 217 | 218 | return false; 219 | } 220 | 221 | protected static class ViewHolder 222 | { 223 | public final TextView name; 224 | public final TextView size; 225 | public final TextView extension; 226 | public final ImageView icon; 227 | 228 | public ViewHolder(View view) 229 | { 230 | this.name = view.findViewById(R.id.name); 231 | this.size = view.findViewById(R.id.size); 232 | this.extension = view.findViewById(R.id.extension); 233 | this.icon = view.findViewById(R.id.icon); 234 | } 235 | } 236 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.app; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.os.Environment; 9 | import android.os.StatFs; 10 | 11 | import com.mauriciotogneri.fileexplorer.R; 12 | import com.mauriciotogneri.fileexplorer.fragments.FolderFragment; 13 | import com.mauriciotogneri.fileexplorer.fragments.StorageFragment; 14 | import com.mauriciotogneri.fileexplorer.models.ButtonBar; 15 | import com.mauriciotogneri.fileexplorer.models.Clipboard; 16 | import com.mauriciotogneri.fileexplorer.models.ToolBar; 17 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 18 | 19 | import java.io.File; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Stack; 23 | 24 | import androidx.appcompat.app.AppCompatActivity; 25 | import androidx.appcompat.widget.Toolbar; 26 | import androidx.core.app.ActivityCompat; 27 | import androidx.core.content.ContextCompat; 28 | import androidx.fragment.app.FragmentManager; 29 | import androidx.fragment.app.FragmentTransaction; 30 | 31 | public class MainActivity extends AppCompatActivity 32 | { 33 | private ToolBar toolBar; 34 | private ButtonBar buttonBar; 35 | private StorageFragment storageFragment = null; 36 | private final Stack fragments = new Stack<>(); 37 | private final Clipboard clipboard = new Clipboard(); 38 | 39 | private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 1000; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) 43 | { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.screen_main); 46 | 47 | Toolbar toolbar = findViewById(R.id.toolbar); 48 | setSupportActionBar(toolbar); 49 | 50 | this.toolBar = new ToolBar(findViewById(R.id.folderName)); 51 | this.buttonBar = new ButtonBar(findViewById(R.id.buttonBar), fragments); 52 | 53 | String[] storages = storages(); 54 | 55 | if (storages.length > 1) 56 | { 57 | storageFragment = StorageFragment.newInstance(storages); 58 | 59 | FragmentManager fragmentManager = getSupportFragmentManager(); 60 | 61 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 62 | transaction.add(R.id.fragmentContainer, storageFragment); 63 | transaction.addToBackStack(null); 64 | transaction.commitAllowingStateLoss(); 65 | 66 | toolBar.update(getString(R.string.app_name)); 67 | } 68 | else 69 | { 70 | String root = Environment.getExternalStorageDirectory().getAbsolutePath(); 71 | FolderFragment folderFragment = FolderFragment.newInstance(root); 72 | 73 | addFragment(folderFragment, false); 74 | } 75 | 76 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 77 | { 78 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) 79 | { 80 | ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_WRITE_EXTERNAL_STORAGE); 81 | } 82 | } 83 | } 84 | 85 | @Override 86 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 87 | { 88 | switch (requestCode) 89 | { 90 | case PERMISSION_WRITE_EXTERNAL_STORAGE: 91 | { 92 | if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) 93 | { 94 | if (storageFragment != null) 95 | { 96 | storageFragment.reload(); 97 | } 98 | else 99 | { 100 | FolderFragment folderFragment = fragments.peek(); 101 | folderFragment.refreshFolder(); 102 | } 103 | } 104 | else 105 | { 106 | finish(); 107 | } 108 | } 109 | } 110 | } 111 | 112 | private String[] storages() 113 | { 114 | List storages = new ArrayList<>(); 115 | 116 | try 117 | { 118 | File[] externalStorageFiles = ContextCompat.getExternalFilesDirs(this, null); 119 | 120 | String base = String.format("/Android/data/%s/files", getPackageName()); 121 | 122 | for (File file : externalStorageFiles) 123 | { 124 | try 125 | { 126 | if (file != null) 127 | { 128 | String path = file.getAbsolutePath(); 129 | 130 | if (path.contains(base)) 131 | { 132 | String finalPath = path.replace(base, ""); 133 | 134 | if (validPath(finalPath)) 135 | { 136 | storages.add(finalPath); 137 | } 138 | } 139 | } 140 | } 141 | catch (Exception e) 142 | { 143 | CrashUtils.report(e); 144 | } 145 | } 146 | } 147 | catch (Exception e) 148 | { 149 | CrashUtils.report(e); 150 | } 151 | 152 | String[] result = new String[storages.size()]; 153 | storages.toArray(result); 154 | 155 | return result; 156 | } 157 | 158 | private boolean validPath(String path) 159 | { 160 | try 161 | { 162 | StatFs stat = new StatFs(path); 163 | stat.getBlockCount(); 164 | 165 | return true; 166 | } 167 | catch (Exception e) 168 | { 169 | CrashUtils.report(e); 170 | 171 | return false; 172 | } 173 | } 174 | 175 | public void addFragment(FolderFragment fragment, boolean addToBackStack) 176 | { 177 | fragments.push(fragment); 178 | 179 | FragmentManager fragmentManager = getSupportFragmentManager(); 180 | 181 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 182 | 183 | if (addToBackStack) 184 | { 185 | transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_right, R.anim.enter_from_right, R.anim.exit_to_right); 186 | } 187 | 188 | transaction.add(R.id.fragmentContainer, fragment); 189 | 190 | if (addToBackStack) 191 | { 192 | transaction.addToBackStack(null); 193 | } 194 | 195 | transaction.commitAllowingStateLoss(); 196 | 197 | toolBar.update(fragment); 198 | } 199 | 200 | private void removeFragment(FolderFragment fragment) 201 | { 202 | fragments.pop(); 203 | 204 | FragmentManager fragmentManager = getSupportFragmentManager(); 205 | 206 | FragmentTransaction transaction = fragmentManager.beginTransaction(); 207 | transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_right, R.anim.enter_from_right, R.anim.exit_to_right); 208 | transaction.remove(fragment); 209 | transaction.commitAllowingStateLoss(); 210 | 211 | if (!fragments.isEmpty()) 212 | { 213 | FolderFragment topFragment = fragments.peek(); 214 | topFragment.refreshFolder(); 215 | 216 | toolBar.update(topFragment); 217 | } 218 | } 219 | 220 | public Clipboard clipboard() 221 | { 222 | return clipboard; 223 | } 224 | 225 | public ButtonBar buttonBar() 226 | { 227 | return buttonBar; 228 | } 229 | 230 | @Override 231 | public void onBackPressed() 232 | { 233 | if (fragments.size() > 0) 234 | { 235 | FolderFragment fragment = fragments.peek(); 236 | 237 | if (fragment.onBackPressed()) 238 | { 239 | if (storageFragment == null) 240 | { 241 | if (fragments.size() > 1) 242 | { 243 | removeFragment(fragment); 244 | } 245 | else 246 | { 247 | finish(); 248 | } 249 | } 250 | else 251 | { 252 | removeFragment(fragment); 253 | 254 | if (fragments.isEmpty()) 255 | { 256 | toolBar.update(getString(R.string.app_name)); 257 | buttonBar.displayButtons(0, false, false, false, false); 258 | } 259 | } 260 | } 261 | } 262 | else 263 | { 264 | finish(); 265 | } 266 | } 267 | 268 | @Override 269 | @SuppressLint("MissingSuperCall") 270 | protected void onSaveInstanceState(Bundle outState) 271 | { 272 | // no call for super(). Bug on API Level > 11. 273 | } 274 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/models/FileInfo.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.models; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.webkit.MimeTypeMap; 9 | 10 | import com.mauriciotogneri.fileexplorer.BuildConfig; 11 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 12 | import com.mauriciotogneri.fileexplorer.utils.SpaceFormatter; 13 | 14 | import java.io.Closeable; 15 | import java.io.File; 16 | import java.io.FileInputStream; 17 | import java.io.FileOutputStream; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | import java.lang.ref.SoftReference; 21 | import java.net.URLConnection; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import androidx.core.content.FileProvider; 26 | 27 | public class FileInfo 28 | { 29 | private final File file; 30 | 31 | private String cachedName = null; 32 | private String cachedPath = null; 33 | private String cachedMimeType = null; 34 | private String cachedExtension = null; 35 | private String cachedSize = null; 36 | private Boolean cachedIsImage = null; 37 | private Boolean cachedIsPdf = null; 38 | private Boolean cachedIsAudio = null; 39 | private Boolean cachedIsVideo = null; 40 | private Boolean cachedIsDirectory = null; 41 | private Integer cachedNumberOfChildren = null; 42 | private SoftReference cachedBitmap; 43 | private boolean isSelected = false; 44 | 45 | public FileInfo(File file) 46 | { 47 | this.file = file; 48 | this.cachedBitmap = new SoftReference<>(null); 49 | } 50 | 51 | public List files() 52 | { 53 | List result = new ArrayList<>(); 54 | 55 | if (isDirectory()) 56 | { 57 | for (File currentFile : children()) 58 | { 59 | if (currentFile != null) 60 | { 61 | FileInfo fileInfo = new FileInfo(currentFile); 62 | result.addAll(fileInfo.files()); 63 | } 64 | } 65 | } 66 | else 67 | { 68 | result.add(this); 69 | } 70 | 71 | return result; 72 | } 73 | 74 | public boolean exists() 75 | { 76 | return file.exists(); 77 | } 78 | 79 | public boolean rename(String newName) 80 | { 81 | File newFile = new File(file.getParentFile(), newName); 82 | 83 | return !newFile.exists() && file.renameTo(newFile); 84 | } 85 | 86 | public boolean copy(FileInfo target, boolean delete) 87 | { 88 | if (isDirectory()) 89 | { 90 | File newTargetFolder = new File(target.file, file.getName()); 91 | boolean allCopied = (newTargetFolder.exists() || newTargetFolder.mkdirs()); 92 | 93 | for (File currentFile : children()) 94 | { 95 | if (currentFile != null) 96 | { 97 | FileInfo fileInfo = new FileInfo(currentFile); 98 | File newTarget = new File(target.file, file.getName()); 99 | 100 | allCopied &= (newTarget.exists() || newTarget.mkdirs()) && fileInfo.copy(new FileInfo(newTarget), delete); 101 | } 102 | } 103 | 104 | if (delete && allCopied) 105 | { 106 | delete(); 107 | } 108 | 109 | return allCopied; 110 | } 111 | else 112 | { 113 | boolean copied = copy(file, target.file); 114 | 115 | if (delete && copied) 116 | { 117 | delete(); 118 | } 119 | 120 | return copied; 121 | } 122 | } 123 | 124 | private boolean copy(File source, File destination) 125 | { 126 | File target = new File(destination, source.getName()); 127 | 128 | InputStream inputStream = null; 129 | OutputStream outputStream = null; 130 | 131 | try 132 | { 133 | inputStream = new FileInputStream(source); 134 | outputStream = new FileOutputStream(target); 135 | byte[] buffer = new byte[1024]; 136 | int length; 137 | 138 | while ((length = inputStream.read(buffer)) > 0) 139 | { 140 | outputStream.write(buffer, 0, length); 141 | } 142 | 143 | outputStream.flush(); 144 | 145 | return true; 146 | } 147 | catch (Exception e) 148 | { 149 | CrashUtils.report(e); 150 | 151 | return false; 152 | } 153 | finally 154 | { 155 | close(inputStream); 156 | close(outputStream); 157 | } 158 | } 159 | 160 | private void close(Closeable closeable) 161 | { 162 | try 163 | { 164 | if (closeable != null) 165 | { 166 | closeable.close(); 167 | } 168 | } 169 | catch (Exception e) 170 | { 171 | CrashUtils.report(e); 172 | } 173 | } 174 | 175 | public boolean delete() 176 | { 177 | if (isDirectory()) 178 | { 179 | for (File currentFile : children()) 180 | { 181 | if (currentFile != null) 182 | { 183 | FileInfo fileInfo = new FileInfo(currentFile); 184 | fileInfo.delete(); 185 | } 186 | } 187 | } 188 | 189 | return file.delete(); 190 | } 191 | 192 | public boolean hasFiles() 193 | { 194 | if (isDirectory()) 195 | { 196 | for (File currentFile : children()) 197 | { 198 | if (currentFile != null) 199 | { 200 | FileInfo fileInfo = new FileInfo(currentFile); 201 | 202 | if (fileInfo.hasFiles()) 203 | { 204 | return true; 205 | } 206 | } 207 | } 208 | 209 | return false; 210 | } 211 | else 212 | { 213 | return true; 214 | } 215 | } 216 | 217 | public File parent() 218 | { 219 | return file.getParentFile(); 220 | } 221 | 222 | public String name() 223 | { 224 | if (cachedName == null) 225 | { 226 | cachedName = file.getName(); 227 | } 228 | 229 | return cachedName; 230 | } 231 | 232 | public Uri uri(Context context) 233 | { 234 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 235 | { 236 | try 237 | { 238 | return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); 239 | } 240 | catch (Exception e) 241 | { 242 | return Uri.fromFile(file); 243 | } 244 | } 245 | else 246 | { 247 | return Uri.fromFile(file); 248 | } 249 | } 250 | 251 | public String path() 252 | { 253 | if (cachedPath == null) 254 | { 255 | cachedPath = file.getAbsolutePath(); 256 | } 257 | 258 | return cachedPath; 259 | } 260 | 261 | public String mimeType() 262 | { 263 | if (cachedMimeType == null) 264 | { 265 | cachedMimeType = mimeTypeV1(); 266 | 267 | if (cachedMimeType == null) 268 | { 269 | cachedMimeType = mimeTypeV2(); 270 | 271 | if (cachedMimeType == null) 272 | { 273 | cachedMimeType = "*/*"; 274 | } 275 | } 276 | } 277 | 278 | return cachedMimeType; 279 | } 280 | 281 | private String mimeTypeV1() 282 | { 283 | try 284 | { 285 | return URLConnection.guessContentTypeFromName(file.getAbsolutePath()); 286 | } 287 | catch (Exception e) 288 | { 289 | return null; 290 | } 291 | } 292 | 293 | private String mimeTypeV2() 294 | { 295 | try 296 | { 297 | String extension = MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()); 298 | 299 | return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); 300 | } 301 | catch (Exception e) 302 | { 303 | return null; 304 | } 305 | } 306 | 307 | public boolean isImage() 308 | { 309 | if (cachedIsImage == null) 310 | { 311 | String mimeType = mimeType(); 312 | 313 | cachedIsImage = (mimeType != null) && mimeType.startsWith("image/"); 314 | } 315 | 316 | return cachedIsImage; 317 | } 318 | 319 | public boolean isPdf() 320 | { 321 | if (cachedIsPdf == null) 322 | { 323 | String mimeType = mimeType(); 324 | 325 | cachedIsPdf = (mimeType != null) && mimeType.startsWith("application/pdf"); 326 | } 327 | 328 | return cachedIsPdf; 329 | } 330 | 331 | public boolean isAudio() 332 | { 333 | if (cachedIsAudio == null) 334 | { 335 | String mimeType = mimeType(); 336 | 337 | cachedIsAudio = (mimeType != null) && mimeType.startsWith("audio/"); 338 | } 339 | 340 | return cachedIsAudio; 341 | } 342 | 343 | public boolean isVideo() 344 | { 345 | if (cachedIsVideo == null) 346 | { 347 | String mimeType = mimeType(); 348 | 349 | cachedIsVideo = (mimeType != null) && mimeType.startsWith("video"); 350 | } 351 | 352 | return cachedIsVideo; 353 | } 354 | 355 | public boolean isDirectory() 356 | { 357 | if (cachedIsDirectory == null) 358 | { 359 | cachedIsDirectory = file.isDirectory(); 360 | } 361 | 362 | return cachedIsDirectory; 363 | } 364 | 365 | public int numberOfChildren() 366 | { 367 | if (cachedNumberOfChildren == null) 368 | { 369 | cachedNumberOfChildren = children().length; 370 | } 371 | 372 | return cachedNumberOfChildren; 373 | } 374 | 375 | private File[] children() 376 | { 377 | File[] children = file.listFiles(); 378 | 379 | return (children != null) ? children : new File[0]; 380 | } 381 | 382 | public String extension() 383 | { 384 | if (cachedExtension == null) 385 | { 386 | cachedExtension = ""; 387 | 388 | String name = name(); 389 | 390 | int index = name.lastIndexOf("."); 391 | 392 | if (index > -1) 393 | { 394 | String extension = name.substring(index + 1); 395 | 396 | if (extension.length() <= 4) 397 | { 398 | cachedExtension = extension.toUpperCase(); 399 | } 400 | } 401 | } 402 | 403 | return cachedExtension; 404 | } 405 | 406 | public String size() 407 | { 408 | if (cachedSize == null) 409 | { 410 | SpaceFormatter spaceFormatter = new SpaceFormatter(); 411 | cachedSize = spaceFormatter.format(file.length()); 412 | } 413 | 414 | return cachedSize; 415 | } 416 | 417 | public boolean hasCachedBitmap() 418 | { 419 | return (cachedBitmap.get() != null); 420 | } 421 | 422 | public Bitmap bitmap(int maxSize) 423 | { 424 | Bitmap bitmap = cachedBitmap.get(); 425 | 426 | if (bitmap == null) 427 | { 428 | String path = path(); 429 | 430 | // decode with inJustDecodeBounds=true to check dimensions 431 | BitmapFactory.Options options = new BitmapFactory.Options(); 432 | options.inJustDecodeBounds = true; 433 | BitmapFactory.decodeFile(path, options); 434 | 435 | // calculate inSampleSize 436 | options.inSampleSize = calculateInSampleSize(options, maxSize, maxSize); 437 | 438 | // decode bitmap with inSampleSize set 439 | options.inJustDecodeBounds = false; 440 | 441 | bitmap = BitmapFactory.decodeFile(path, options); 442 | cachedBitmap = new SoftReference<>(bitmap); 443 | } 444 | 445 | return bitmap; 446 | } 447 | 448 | private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) 449 | { 450 | // raw height and width of image 451 | int height = options.outHeight; 452 | int width = options.outWidth; 453 | int inSampleSize = 1; 454 | 455 | if (height > reqHeight || width > reqWidth) 456 | { 457 | int halfHeight = height / 2; 458 | int halfWidth = width / 2; 459 | 460 | // calculate the largest inSampleSize value that is a power of 2 and keeps both 461 | // height and width larger than the requested height and width. 462 | while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) 463 | { 464 | inSampleSize *= 2; 465 | } 466 | } 467 | 468 | return inSampleSize; 469 | } 470 | 471 | public boolean toggleSelection() 472 | { 473 | isSelected = !isSelected; 474 | 475 | return isSelected; 476 | } 477 | 478 | public void select(boolean value) 479 | { 480 | isSelected = value; 481 | } 482 | 483 | public boolean isSelected() 484 | { 485 | return isSelected; 486 | } 487 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mauriciotogneri/fileexplorer/fragments/FolderFragment.java: -------------------------------------------------------------------------------- 1 | package com.mauriciotogneri.fileexplorer.fragments; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.ProgressDialog; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.content.pm.ResolveInfo; 9 | import android.net.Uri; 10 | import android.os.AsyncTask; 11 | import android.os.Bundle; 12 | import android.view.LayoutInflater; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.ListView; 17 | import android.widget.TextView; 18 | import android.widget.Toast; 19 | 20 | import com.mauriciotogneri.fileexplorer.R; 21 | import com.mauriciotogneri.fileexplorer.adapters.FolderAdapter; 22 | import com.mauriciotogneri.fileexplorer.app.MainActivity; 23 | import com.mauriciotogneri.fileexplorer.models.Clipboard; 24 | import com.mauriciotogneri.fileexplorer.models.FileInfo; 25 | import com.mauriciotogneri.fileexplorer.utils.CrashUtils; 26 | import com.mauriciotogneri.fileexplorer.utils.Dialogs; 27 | 28 | import java.io.File; 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.Collections; 32 | import java.util.List; 33 | 34 | import androidx.annotation.StringRes; 35 | import androidx.fragment.app.Fragment; 36 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 37 | 38 | public class FolderFragment extends Fragment 39 | { 40 | private static final String PARAMETER_FOLDER_PATH = "folder.path"; 41 | 42 | private MainActivity mainActivity; 43 | private SwipeRefreshLayout swipeContainer; 44 | private ListView listView; 45 | private TextView labelNoItems; 46 | private FolderAdapter adapter; 47 | 48 | public static FolderFragment newInstance(String folderPath) 49 | { 50 | FolderFragment fragment = new FolderFragment(); 51 | Bundle parameters = new Bundle(); 52 | parameters.putSerializable(PARAMETER_FOLDER_PATH, folderPath); 53 | fragment.setArguments(parameters); 54 | 55 | return fragment; 56 | } 57 | 58 | @Override 59 | public void onAttach(Context context) 60 | { 61 | super.onAttach(context); 62 | 63 | mainActivity = (MainActivity) context; 64 | } 65 | 66 | @Override 67 | public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 68 | { 69 | View view = inflater.inflate(R.layout.screen_folder, container, false); 70 | 71 | swipeContainer = view.findViewById(R.id.swipeContainer); 72 | listView = view.findViewById(R.id.list); 73 | labelNoItems = view.findViewById(R.id.label_noItems); 74 | 75 | return view; 76 | } 77 | 78 | @Override 79 | @SuppressLint("ClickableViewAccessibility") 80 | public final void onActivityCreated(Bundle savedInstanceState) 81 | { 82 | super.onActivityCreated(savedInstanceState); 83 | 84 | swipeContainer.setColorSchemeResources(R.color.blue1); 85 | swipeContainer.setOnRefreshListener(() -> { 86 | refreshFolder(); 87 | swipeContainer.setRefreshing(false); 88 | }); 89 | 90 | adapter = new FolderAdapter(mainActivity); 91 | 92 | listView.setAdapter(adapter); 93 | listView.setOnItemClickListener((parent, view, position, id) -> { 94 | FileInfo fileInfo = (FileInfo) parent.getItemAtPosition(position); 95 | 96 | if (adapter.isSelectionMode()) 97 | { 98 | adapter.updateSelection(fileInfo.toggleSelection()); 99 | updateButtonBar(); 100 | } 101 | else 102 | { 103 | if (fileInfo.isDirectory()) 104 | { 105 | openFolder(fileInfo); 106 | } 107 | else 108 | { 109 | openFile(fileInfo); 110 | } 111 | } 112 | }); 113 | 114 | listView.setOnItemLongClickListener((parent, view, position, id) -> { 115 | FileInfo fileInfo = (FileInfo) parent.getItemAtPosition(position); 116 | adapter.updateSelection(fileInfo.toggleSelection()); 117 | updateButtonBar(); 118 | 119 | return true; 120 | }); 121 | 122 | listView.setOnTouchListener((v, event) -> { 123 | if ((event.getAction() == MotionEvent.ACTION_DOWN) && listView.pointToPosition((int) (event.getX() * event.getXPrecision()), (int) (event.getY() * event.getYPrecision())) == -1) 124 | { 125 | onBackPressed(); 126 | 127 | return true; 128 | } 129 | 130 | return false; 131 | }); 132 | 133 | refreshFolder(); 134 | } 135 | 136 | public synchronized boolean onBackPressed() 137 | { 138 | if ((adapter != null) && adapter.isSelectionMode()) 139 | { 140 | unselectAll(); 141 | 142 | return false; 143 | } 144 | else 145 | { 146 | return true; 147 | } 148 | } 149 | 150 | private void unselectAll() 151 | { 152 | adapter.unselectAll(); 153 | updateButtonBar(); 154 | } 155 | 156 | private void updateButtonBar() 157 | { 158 | Clipboard clipboard = mainActivity.clipboard(); 159 | 160 | mainActivity.buttonBar().displayButtons(adapter.itemsSelected(), !adapter.allItemsSelected(), !clipboard.isEmpty() && clipboard.someExist() && !clipboard.hasParent(folder()), adapter.hasFiles(), true); 161 | } 162 | 163 | public String folderName() 164 | { 165 | return folder().getAbsolutePath(); 166 | } 167 | 168 | private File folder() 169 | { 170 | String folderPath = parameter(PARAMETER_FOLDER_PATH, "/"); 171 | 172 | return new File(folderPath); 173 | } 174 | 175 | private List fileList() 176 | { 177 | File root = folder(); 178 | File[] fileArray = root.listFiles(); 179 | 180 | if (fileArray != null) 181 | { 182 | List files = Arrays.asList(fileArray); 183 | 184 | Collections.sort(files, (lhs, rhs) -> { 185 | if (lhs.isDirectory() && !rhs.isDirectory()) 186 | { 187 | return -1; 188 | } 189 | else if (!lhs.isDirectory() && rhs.isDirectory()) 190 | { 191 | return 1; 192 | } 193 | else 194 | { 195 | return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase()); 196 | } 197 | }); 198 | 199 | List result = new ArrayList<>(); 200 | 201 | for (File file : files) 202 | { 203 | if (file != null) 204 | { 205 | result.add(new FileInfo(file)); 206 | } 207 | } 208 | 209 | return result; 210 | } 211 | else 212 | { 213 | return new ArrayList<>(); 214 | } 215 | } 216 | 217 | @SuppressWarnings({"unchecked", "SameParameterValue"}) 218 | private Type parameter(String key, Type defaultValue) 219 | { 220 | Bundle extras = getArguments(); 221 | 222 | if ((extras != null) && extras.containsKey(key)) 223 | { 224 | return (Type) extras.get(key); 225 | } 226 | else 227 | { 228 | return defaultValue; 229 | } 230 | } 231 | 232 | private void openFolder(FileInfo fileInfo) 233 | { 234 | FolderFragment folderFragment = FolderFragment.newInstance(fileInfo.path()); 235 | 236 | mainActivity.addFragment(folderFragment, true); 237 | } 238 | 239 | private void openFile(FileInfo fileInfo) 240 | { 241 | try 242 | { 243 | String type = fileInfo.mimeType(); 244 | Intent intent = openFileIntent(fileInfo.uri(context()), type); 245 | 246 | if (isResolvable(intent)) 247 | { 248 | startActivity(intent, R.string.open_unable); 249 | } 250 | else 251 | { 252 | defaultOpenFile(fileInfo); 253 | } 254 | } 255 | catch (Exception e) 256 | { 257 | defaultOpenFile(fileInfo); 258 | } 259 | } 260 | 261 | private Intent openFileIntent(Uri uri, String type) 262 | { 263 | Intent intent = new Intent(Intent.ACTION_VIEW); 264 | intent.setDataAndType(uri, type); 265 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 266 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 267 | 268 | return intent; 269 | } 270 | 271 | private void defaultOpenFile(FileInfo fileInfo) 272 | { 273 | try 274 | { 275 | Intent intent = new Intent(Intent.ACTION_VIEW, fileInfo.uri(context())); 276 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 277 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 278 | } 279 | catch (Exception e) 280 | { 281 | CrashUtils.report(e); 282 | 283 | showMessage(R.string.open_unable); 284 | } 285 | } 286 | 287 | public void onCut() 288 | { 289 | List items = adapter.selectedItems(false); 290 | mainActivity.clipboard().cut(items); 291 | unselectAll(); 292 | } 293 | 294 | public void onCopy() 295 | { 296 | List items = adapter.selectedItems(false); 297 | mainActivity.clipboard().copy(items); 298 | unselectAll(); 299 | } 300 | 301 | @SuppressLint("StaticFieldLeak") 302 | public void onPaste() 303 | { 304 | Clipboard clipboard = mainActivity.clipboard(); 305 | 306 | String message = ""; 307 | 308 | if (clipboard.isCut()) 309 | { 310 | message = getString(R.string.clipboard_cut); 311 | } 312 | else if (clipboard.isCopy()) 313 | { 314 | message = getString(R.string.clipboard_copy); 315 | } 316 | 317 | ProgressDialog dialog = Dialogs.progress(context(), message); 318 | 319 | new AsyncTask() 320 | { 321 | @Override 322 | protected Void doInBackground(Void... params) 323 | { 324 | clipboard.paste(new FileInfo(folder())); 325 | 326 | return null; 327 | } 328 | 329 | @Override 330 | protected void onPostExecute(Void result) 331 | { 332 | try 333 | { 334 | dialog.dismiss(); 335 | } 336 | catch (Exception e) 337 | { 338 | CrashUtils.report(e); 339 | } 340 | 341 | refreshFolder(); 342 | } 343 | }.execute(); 344 | } 345 | 346 | public void onSelectAll() 347 | { 348 | adapter.selectAll(); 349 | updateButtonBar(); 350 | } 351 | 352 | public void onRename() 353 | { 354 | List items = adapter.selectedItems(false); 355 | 356 | if (items.size() == 1) 357 | { 358 | Dialogs.rename(context(), items.get(0), this::renameItem); 359 | } 360 | } 361 | 362 | public void onShare() 363 | { 364 | List selectedItems = adapter.selectedItems(true); 365 | 366 | if (selectedItems.size() == 1) 367 | { 368 | shareSingle(selectedItems.get(0)); 369 | } 370 | else if (!selectedItems.isEmpty()) 371 | { 372 | shareMultiple(selectedItems); 373 | } 374 | } 375 | 376 | private void shareSingle(FileInfo fileInfo) 377 | { 378 | try 379 | { 380 | String type = fileInfo.mimeType(); 381 | Intent intent = shareSingleIntent(fileInfo.uri(context()), type); 382 | 383 | if (isResolvable(intent)) 384 | { 385 | startActivity(intent, R.string.shareFile_unable); 386 | } 387 | else 388 | { 389 | showMessage(R.string.shareFile_unable); 390 | } 391 | } 392 | catch (Exception e) 393 | { 394 | CrashUtils.report(e); 395 | 396 | showMessage(R.string.shareFile_unable); 397 | } 398 | } 399 | 400 | private Intent shareSingleIntent(Uri uri, String type) 401 | { 402 | Intent intent = new Intent(Intent.ACTION_SEND); 403 | intent.setType(type); 404 | intent.putExtra(Intent.EXTRA_STREAM, uri); 405 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 406 | 407 | return intent; 408 | } 409 | 410 | private void shareMultiple(List list) 411 | { 412 | try 413 | { 414 | Intent intent = shareMultipleIntent(list); 415 | 416 | if (isResolvable(intent)) 417 | { 418 | startActivity(intent, R.string.shareFiles_unable); 419 | } 420 | else 421 | { 422 | showMessage(R.string.shareFiles_unable); 423 | } 424 | } 425 | catch (Exception e) 426 | { 427 | CrashUtils.report(e); 428 | 429 | showMessage(R.string.shareFiles_unable); 430 | } 431 | } 432 | 433 | private Intent shareMultipleIntent(List list) 434 | { 435 | Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); 436 | intent.setType("*/*"); 437 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 438 | 439 | ArrayList files = new ArrayList<>(); 440 | 441 | for (FileInfo fileInfo : list) 442 | { 443 | files.add(fileInfo.uri(context())); 444 | } 445 | 446 | intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files); 447 | 448 | return Intent.createChooser(intent, getString(R.string.shareFile_title)); 449 | } 450 | 451 | public void onDelete() 452 | { 453 | Dialogs.delete(context(), adapter, this::deleteSelected); 454 | } 455 | 456 | public void onCreate() 457 | { 458 | Dialogs.create(context(), this::createFolder); 459 | } 460 | 461 | private void createFolder(String name) 462 | { 463 | File parent = folder(); 464 | File newFolder = new File(parent, name); 465 | 466 | if (newFolder.mkdir()) 467 | { 468 | refreshFolder(); 469 | } 470 | else 471 | { 472 | showMessage(R.string.create_error); 473 | } 474 | } 475 | 476 | @SuppressLint("StaticFieldLeak") 477 | private void deleteSelected(List selectedItems) 478 | { 479 | ProgressDialog dialog = Dialogs.progress(context(), getString(R.string.delete_deleting)); 480 | 481 | new AsyncTask() 482 | { 483 | @Override 484 | protected Boolean doInBackground(Void... params) 485 | { 486 | boolean allDeleted = true; 487 | 488 | for (FileInfo fileInfo : selectedItems) 489 | { 490 | if (!fileInfo.delete()) 491 | { 492 | allDeleted = false; 493 | } 494 | } 495 | 496 | return allDeleted; 497 | } 498 | 499 | @Override 500 | protected void onPostExecute(Boolean result) 501 | { 502 | try 503 | { 504 | dialog.dismiss(); 505 | } 506 | catch (Exception e) 507 | { 508 | CrashUtils.report(e); 509 | } 510 | 511 | refreshFolder(); 512 | 513 | if (!result) 514 | { 515 | showMessage(R.string.delete_error); 516 | } 517 | } 518 | }.execute(); 519 | } 520 | 521 | private void renameItem(FileInfo fileInfo, String newName) 522 | { 523 | if (fileInfo.rename(newName)) 524 | { 525 | refreshFolder(); 526 | } 527 | else 528 | { 529 | showMessage(R.string.rename_error); 530 | } 531 | } 532 | 533 | private void showMessage(@StringRes int text) 534 | { 535 | Toast.makeText(context(), text, Toast.LENGTH_SHORT).show(); 536 | } 537 | 538 | public void refreshFolder() 539 | { 540 | List files = fileList(); 541 | adapter.setData(files); 542 | updateButtonBar(); 543 | 544 | if (files.isEmpty()) 545 | { 546 | listView.setVisibility(View.GONE); 547 | labelNoItems.setVisibility(View.VISIBLE); 548 | } 549 | else 550 | { 551 | listView.setVisibility(View.VISIBLE); 552 | labelNoItems.setVisibility(View.GONE); 553 | } 554 | } 555 | 556 | private void startActivity(Intent intent, @StringRes int resId) 557 | { 558 | try 559 | { 560 | startActivity(intent); 561 | } 562 | catch (Exception e) 563 | { 564 | CrashUtils.report(e); 565 | 566 | showMessage(resId); 567 | } 568 | } 569 | 570 | private boolean isResolvable(Intent intent) 571 | { 572 | PackageManager manager = mainActivity.getPackageManager(); 573 | List resolveInfo = manager.queryIntentActivities(intent, 0); 574 | 575 | return !resolveInfo.isEmpty(); 576 | } 577 | 578 | private Context context() 579 | { 580 | Context context = getContext(); 581 | 582 | if (context != null) 583 | { 584 | return context; 585 | } 586 | else 587 | { 588 | Context fragmentActivity = getActivity(); 589 | 590 | if (fragmentActivity != null) 591 | { 592 | return fragmentActivity; 593 | } 594 | else 595 | { 596 | return mainActivity; 597 | } 598 | } 599 | } 600 | 601 | @Override 602 | public void onSaveInstanceState(Bundle outState) 603 | { 604 | // no call for super(). Bug on API Level > 11. 605 | } 606 | } --------------------------------------------------------------------------------