├── 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 | }
--------------------------------------------------------------------------------