diff --git a/.github/workflows/v3-release.yml b/.github/workflows/v3-release.yml
index 4f75cc7..d3bd519 100644
--- a/.github/workflows/v3-release.yml
+++ b/.github/workflows/v3-release.yml
@@ -13,21 +13,21 @@ on:
jobs:
build:
name: Setup Environment and build
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- name: Checkout the code to specific branch
- uses: actions/checkout@v5
+ uses: actions/checkout@v7
with:
ref: ${{ github.event.inputs.branch }}
- name: Set up JDK
- uses: actions/setup-java@v5
+ uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
- uses: android-actions/setup-android@v3
+ uses: android-actions/setup-android@v4
- name: Make gradlew executable
run: chmod +x ./gradlew
@@ -117,7 +117,7 @@ jobs:
- name: Create Github Release
id: create_release
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@v3
with:
prerelease: ${{ env.BUILD_TARGET == 'debug' }}
tag_name: ${{ env.VERSION_TAG }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bcbb3cf..45de67b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,13 @@ All notable changes to this project will be documented in this file.
- **TBD** Configuration response error handling
+## [3.5.3] - 2026-09-29
+
+### Removed
+
+- Removed unused READ_MEDIA_IMAGES, READ_MEDIA_VIDEO and READ_MEDIA_AUDIO permissions that blocked Google Play uploads under the Photo and Video Permissions policy
+- Removed unnecessary storage permission prompts before export and invoice sharing
+
## [3.5.2] - 2026-09-01
### Fixed
diff --git a/app/build.gradle b/app/build.gradle
index 1c5840b..9aabd65 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ apply plugin: 'com.google.firebase.crashlytics'
def versionMajor = 3
def versionMinor = 5
-def versionPatch = 2
+def versionPatch = 3
def versionBuild = 1
def appVersionBuild = Integer.valueOf(System.env.GITHUB_RUN_NUMBER ?: versionBuild)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d104864..921503c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,11 +6,6 @@
-
-
-
-
-
= Build.VERSION_CODES.Q) {
- startCatalogExport()
- } else {
- attemptCatalogExport()
- }
+ startCatalogExport()
}
binding.catalogImportButton.setOnClickListener {
@@ -262,27 +255,6 @@ class CatalogDashFragment : Fragment() {
}
}
- private fun attemptCatalogExport() {
- Dexter.withContext(activity)
- .withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .withListener(object : PermissionListener {
- override fun onPermissionGranted(response: PermissionGrantedResponse) {/* ... */
- startCatalogExport()
- }
-
- override fun onPermissionDenied(response: PermissionDeniedResponse) {/* ... */
- toast(getString(R.string.denied_permission))
- }
-
- override fun onPermissionRationaleShouldBeShown(
- permission: PermissionRequest,
- token: PermissionToken
- ) {/* ... */
- token.continuePermissionRequest()
- }
- }).check()
- }
-
@SuppressLint("CheckResult")
private fun startCatalogExport() {
MaterialDialog(requireContext()).show {
diff --git a/app/src/main/java/online/taxcore/pos/ui/dashboard/DashboardActivity.kt b/app/src/main/java/online/taxcore/pos/ui/dashboard/DashboardActivity.kt
index fe3bcb9..db03988 100644
--- a/app/src/main/java/online/taxcore/pos/ui/dashboard/DashboardActivity.kt
+++ b/app/src/main/java/online/taxcore/pos/ui/dashboard/DashboardActivity.kt
@@ -295,8 +295,6 @@ class DashboardActivity : BaseActivity() {
private fun askPermission() {
Dexter.withContext(this).withPermissions(
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
).withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {}
diff --git a/app/src/main/java/online/taxcore/pos/ui/invoice/FiscalInvoiceFragment.kt b/app/src/main/java/online/taxcore/pos/ui/invoice/FiscalInvoiceFragment.kt
index 9daeedd..3d06bf7 100644
--- a/app/src/main/java/online/taxcore/pos/ui/invoice/FiscalInvoiceFragment.kt
+++ b/app/src/main/java/online/taxcore/pos/ui/invoice/FiscalInvoiceFragment.kt
@@ -1,12 +1,10 @@
package online.taxcore.pos.ui.invoice
// import online.taxcore.pos.utils.CreatePdf // TODO: Migrate to iText7
-import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
-import android.os.Build
import android.os.Bundle
import android.print.PrintManager
import android.util.Base64
@@ -19,12 +17,6 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.preference.PreferenceManager
import com.bumptech.glide.Glide
-import com.karumi.dexter.Dexter
-import com.karumi.dexter.PermissionToken
-import com.karumi.dexter.listener.PermissionDeniedResponse
-import com.karumi.dexter.listener.PermissionGrantedResponse
-import com.karumi.dexter.listener.PermissionRequest
-import com.karumi.dexter.listener.single.PermissionListener
import online.taxcore.pos.BuildConfig
import online.taxcore.pos.R
import online.taxcore.pos.constants.PrefConstants
@@ -32,7 +24,6 @@ import online.taxcore.pos.databinding.InvoicePreviewDialogBinding
import online.taxcore.pos.enums.ExportMimeType
import online.taxcore.pos.ui.base.BaseActivity
import online.taxcore.pos.utils.CreatePdf
-import online.taxcore.pos.utils.toast
import java.io.File
class FiscalInvoiceFragment : DialogFragment() {
@@ -101,40 +92,9 @@ class FiscalInvoiceFragment : DialogFragment() {
}
binding.mainAppBarShare.setOnClickListener {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- invoiceNumber?.let { invoiceNo ->
- createAndSharePdf(
- invoiceNo,
- invoiceJournal
- )
-
- }
- return@setOnClickListener
+ invoiceNumber?.let { invoiceNo ->
+ createAndSharePdf(invoiceNo, invoiceJournal)
}
-
- Dexter.withContext(activity)
- .withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .withListener(object : PermissionListener {
- override fun onPermissionGranted(response: PermissionGrantedResponse) {/* ... */
- invoiceNumber?.let { it1 ->
- createAndSharePdf(
- it1,
- invoiceJournal
- )
- }
- }
-
- override fun onPermissionDenied(response: PermissionDeniedResponse) {/* ... */
- toast(getString(R.string.denied_permission))
- }
-
- override fun onPermissionRationaleShouldBeShown(
- permission: PermissionRequest,
- token: PermissionToken
- ) {/* ... */
- token.continuePermissionRequest()
- }
- }).check()
}
binding.mainAppBarPrint.setOnClickListener {
diff --git a/app/src/main/java/online/taxcore/pos/ui/journal/JournalDashFragment.kt b/app/src/main/java/online/taxcore/pos/ui/journal/JournalDashFragment.kt
index 3bdb676..afbefec 100644
--- a/app/src/main/java/online/taxcore/pos/ui/journal/JournalDashFragment.kt
+++ b/app/src/main/java/online/taxcore/pos/ui/journal/JournalDashFragment.kt
@@ -31,11 +31,8 @@ import com.vicpin.krealmextensions.deleteAll
import io.realm.Realm
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
-import com.karumi.dexter.listener.PermissionDeniedResponse
-import com.karumi.dexter.listener.PermissionGrantedResponse
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
-import com.karumi.dexter.listener.single.PermissionListener
import dagger.android.support.AndroidSupportInjection
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -140,11 +137,7 @@ class JournalDashFragment : Fragment() {
}
binding.journalExportButton.setOnClickListener {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- startJournalExport()
- } else {
- attemptJournalExport()
- }
+ startJournalExport()
}
// Secret long press (5 seconds) to export and clear journal
@@ -152,11 +145,7 @@ class JournalDashFragment : Fragment() {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
secretExportRunnable = Runnable {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- startJournalExportAndClear()
- } else {
- attemptJournalExportAndClear()
- }
+ startJournalExportAndClear()
}
secretExportHandler.postDelayed(secretExportRunnable!!, SECRET_HOLD_DURATION)
false
@@ -217,25 +206,6 @@ class JournalDashFragment : Fragment() {
}
}
- private fun attemptJournalExport() {
- Dexter.withContext(activity).withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .withListener(object : PermissionListener {
- override fun onPermissionGranted(response: PermissionGrantedResponse) {/* ... */
- startJournalExport()
- }
-
- override fun onPermissionDenied(response: PermissionDeniedResponse) {/* ... */
- toast(getString(R.string.denied_permission))
- }
-
- override fun onPermissionRationaleShouldBeShown(
- permission: PermissionRequest, token: PermissionToken
- ) {/* ... */
- token.continuePermissionRequest()
- }
- }).check()
- }
-
private fun startJournalExport() {
MaterialDialog(requireContext()).show {
title(R.string.title_export_journal)
@@ -257,25 +227,6 @@ class JournalDashFragment : Fragment() {
}
}
- private fun attemptJournalExportAndClear() {
- Dexter.withContext(activity).withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .withListener(object : PermissionListener {
- override fun onPermissionGranted(response: PermissionGrantedResponse) {
- startJournalExportAndClear()
- }
-
- override fun onPermissionDenied(response: PermissionDeniedResponse) {
- toast(getString(R.string.denied_permission))
- }
-
- override fun onPermissionRationaleShouldBeShown(
- permission: PermissionRequest, token: PermissionToken
- ) {
- token.continuePermissionRequest()
- }
- }).check()
- }
-
private fun startJournalExportAndClear() {
MaterialDialog(requireContext()).show {
title(R.string.dialog_title_export_and_clear)
diff --git a/app/src/main/java/online/taxcore/pos/utils/FileUtils.java b/app/src/main/java/online/taxcore/pos/utils/FileUtils.java
index 922cc32..5b1f7d9 100644
--- a/app/src/main/java/online/taxcore/pos/utils/FileUtils.java
+++ b/app/src/main/java/online/taxcore/pos/utils/FileUtils.java
@@ -1,404 +1,18 @@
package online.taxcore.pos.utils;
-import android.content.ContentUris;
import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.net.Uri;
import android.os.Environment;
import android.os.StatFs;
-import android.provider.DocumentsContract;
-import android.provider.MediaStore;
-import android.provider.OpenableColumns;
import android.util.Log;
-import android.webkit.MimeTypeMap;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.FileProvider;
-
-import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.DecimalFormat;
-import java.util.Comparator;
-
-import okhttp3.ResponseBody;
public class FileUtils {
- public static final String DOCUMENTS_DIR = "documents";
- // configured android:authorities in AndroidManifest (https://developer.android.com/reference/android/support/v4/content/FileProvider)
- public static final String AUTHORITY = "external_files";
- public static final String HIDDEN_PREFIX = ".";
/**
* TAG for log messages.
*/
static final String TAG = "FileUtils";
private static final boolean DEBUG = true; // Set to true to enable logging
- /**
- * File and folder comparator. TODO Expose sorting option method
- */
- public static Comparator sComparator = (f1, f2) -> {
- // Sort alphabetically by lower case, which is much cleaner
- return f1.getName().toLowerCase().compareTo(
- f2.getName().toLowerCase());
- };
- /**
- * File (not directories) filter.
- */
- public static FileFilter sFileFilter = file -> {
- final String fileName = file.getName();
- // Return files only (not directories) and skip hidden files
- return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX);
- };
- /**
- * Folder (directories) filter.
- */
- public static FileFilter sDirFilter = file -> {
- final String fileName = file.getName();
- // Return directories only and skip hidden directories
- return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX);
- };
-
- private FileUtils() {
- } //private constructor to enforce Singleton pattern
-
- /**
- * Gets the extension of a file name, like ".png" or ".jpg".
- *
- * @param uri
- * @return Extension including the dot("."); "" if there is no extension;
- * null if uri was null.
- */
- public static String getExtension(String uri) {
- if (uri == null) {
- return null;
- }
-
- int dot = uri.lastIndexOf(".");
- if (dot >= 0) {
- return uri.substring(dot);
- } else {
- // No extension.
- return "";
- }
- }
-
- /**
- * @return Whether the URI is a local one.
- */
- public static boolean isLocal(String url) {
- return url != null && !url.startsWith("http://") && !url.startsWith("https://");
- }
-
- /**
- * @return True if Uri is a MediaStore Uri.
- * @author paulburke
- */
- public static boolean isMediaUri(Uri uri) {
- return "media".equalsIgnoreCase(uri.getAuthority());
- }
-
- /**
- * Convert File into Uri.
- *
- * @param file
- * @return uri
- */
- public static Uri getUri(File file) {
- return (file != null) ? Uri.fromFile(file) : null;
- }
-
- /**
- * Returns the path only (without file name).
- *
- * @param file
- * @return
- */
- public static File getPathWithoutFilename(File file) {
- if (file != null) {
- if (file.isDirectory()) {
- // no file to be split off. Return everything
- return file;
- } else {
- String filename = file.getName();
- String filepath = file.getAbsolutePath();
-
- // Construct path without file name.
- String pathwithoutname = filepath.substring(0,
- filepath.length() - filename.length());
- if (pathwithoutname.endsWith("/")) {
- pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1);
- }
- return new File(pathwithoutname);
- }
- }
- return null;
- }
-
- /**
- * @return The MIME type for the given file.
- */
- public static String getMimeType(File file) {
-
- String extension = getExtension(file.getName());
-
- if (extension.length() > 0)
- return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
-
- return "application/octet-stream";
- }
-
- /**
- * @return The MIME type for the give Uri.
- */
- public static String getMimeType(Context context, Uri uri) {
- File file = new File(getPath(context, uri));
- return getMimeType(file);
- }
-
- /**
- * @return The MIME type for the give String Uri.
- */
- public static String getMimeType(Context context, String url) {
- String type = context.getContentResolver().getType(Uri.parse(url));
- if (type == null) {
- type = "application/octet-stream";
- }
- return type;
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is local.
- */
- public static boolean isLocalStorageDocument(Uri uri) {
- return AUTHORITY.equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- public static boolean isExternalStorageDocument(Uri uri) {
- return "com.android.externalstorage.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is DownloadsProvider.
- */
- public static boolean isDownloadsDocument(Uri uri) {
- return "com.android.providers.downloads.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is MediaProvider.
- */
- public static boolean isMediaDocument(Uri uri) {
- return "com.android.providers.media.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is Google Photos.
- */
- public static boolean isGooglePhotosUri(Uri uri) {
- return "com.google.android.apps.photos.content".equals(uri.getAuthority());
- }
-
- /**
- * Get the value of the data column for this Uri. This is useful for
- * MediaStore Uris, and other file-based ContentProviders.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @param selection (Optional) Filter used in the query.
- * @param selectionArgs (Optional) Selection arguments used in the query.
- * @return The value of the _data column, which is typically a file path.
- */
- public static String getDataColumn(Context context, Uri uri, String selection,
- String[] selectionArgs) {
-
- Cursor cursor = null;
- final String column = MediaStore.Files.FileColumns.DATA;
- final String[] projection = {
- column
- };
-
- try {
- cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
- null);
- if (cursor != null && cursor.moveToFirst()) {
- if (DEBUG)
- DatabaseUtils.dumpCursor(cursor);
-
- final int column_index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(column_index);
- }
- } catch (Exception e) {
- Log.e("", e.toString());
- } finally {
- if (cursor != null)
- cursor.close();
- }
- return null;
- }
-
- /**
- * Get a file path from a Uri. This will get the the path for Storage Access
- * Framework Documents, as well as the _data field for the MediaStore and
- * other file-based ContentProviders.
- *
- * Callers should check whether the path is local before assuming it
- * represents a local file.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @see #isLocal(String)
- * @see #getFile(Context, Uri)
- */
- public static String getPath(final Context context, final Uri uri) {
- String absolutePath = getLocalPath(context, uri);
- return absolutePath != null ? absolutePath : uri.toString();
- }
-
- private static String getLocalPath(final Context context, final Uri uri) {
-
- if (DEBUG)
- Log.d(TAG + " File -",
- "Authority: " + uri.getAuthority() +
- ", Fragment: " + uri.getFragment() +
- ", Port: " + uri.getPort() +
- ", Query: " + uri.getQuery() +
- ", Scheme: " + uri.getScheme() +
- ", Host: " + uri.getHost() +
- ", Segments: " + uri.getPathSegments().toString()
- );
-
-
- // DocumentProvider
- if (DocumentsContract.isDocumentUri(context, uri)) {
- // LocalStorageProvider
- if (isLocalStorageDocument(uri)) {
- // The path is the id
- return DocumentsContract.getDocumentId(uri);
- }
- // ExternalStorageProvider
- else if (isExternalStorageDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- if ("primary".equalsIgnoreCase(type)) {
- return Environment.getExternalStorageDirectory() + "/" + split[1];
- } else if ("home".equalsIgnoreCase(type)) {
- return Environment.getExternalStorageDirectory() + "/documents/" + split[1];
- }
- }
- // DownloadsProvider
- else if (isDownloadsDocument(uri)) {
-
- final String id = DocumentsContract.getDocumentId(uri);
-
- if (id != null && id.startsWith("raw:")) {
- return id.substring(4);
- }
-
- String[] contentUriPrefixesToTry = new String[]{
- "content://downloads/public_downloads",
- "content://downloads/my_downloads",
- "content://downloads/all_downloads"
- };
-
- for (String contentUriPrefix : contentUriPrefixesToTry) {
- Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
- try {
- String path = getDataColumn(context, contentUri, null, null);
- if (path != null) {
- return path;
- }
- } catch (Exception e) {
- }
- }
-
- // path could not be retrieved using ContentResolver, therefore copy file to accessible cache using streams
- String fileName = getFileName(context, uri);
- File cacheDir = getDocumentCacheDir(context);
- File file = generateFileName(fileName, cacheDir);
- String destinationPath = null;
- if (file != null) {
- destinationPath = file.getAbsolutePath();
- saveFileFromUri(context, uri, destinationPath);
- }
-
- return destinationPath;
- }
- // MediaProvider
- else if (isMediaDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- Uri contentUri = null;
- if ("image".equals(type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- }
-
- final String selection = "_id=?";
- final String[] selectionArgs = new String[]{
- split[1]
- };
-
- return getDataColumn(context, contentUri, selection, selectionArgs);
- }
- }
- // MediaStore (and general)
- else if ("content".equalsIgnoreCase(uri.getScheme())) {
-
- // Return the remote address
- if (isGooglePhotosUri(uri)) {
- return uri.getLastPathSegment();
- }
-
- return getDataColumn(context, uri, null, null);
- }
- // File
- else if ("file".equalsIgnoreCase(uri.getScheme())) {
- return uri.getPath();
- }
-
- return null;
- }
-
- /**
- * Convert Uri into File, if possible.
- *
- * @return file A local file that the Uri was pointing to, or null if the
- * Uri is unsupported or pointed to a remote resource.
- * @author paulburke
- * @see #getPath(Context, Uri)
- */
- public static File getFile(Context context, Uri uri) {
- if (uri != null) {
- String path = getPath(context, uri);
- if (isLocal(path)) {
- return new File(path);
- }
- }
- return null;
- }
/**
* @return Number of kilo bytes available on External storage
@@ -410,342 +24,6 @@ public static long getAvailableSpaceInKB() {
return availableSpace / SIZE_KB;
}
- public static float storageAvailableKB(File file) {
- final long SIZE_KB = 1024L;
- StatFs stat = new StatFs(file.getPath());
- long bytesAvailable = stat.getBlockSizeLong() * stat.getAvailableBlocksLong();
-
- return bytesAvailable / SIZE_KB;
- }
-
- /**
- * Get the file size in a human-readable string.
- *
- * @param size
- * @return
- * @author paulburke
- */
- public static String getReadableFileSize(int size) {
- final int BYTES_IN_KILOBYTES = 1024;
- final DecimalFormat dec = new DecimalFormat("###.#");
- final String KILOBYTES = " KB";
- final String MEGABYTES = " MB";
- final String GIGABYTES = " GB";
- float fileSize = 0;
- String suffix = KILOBYTES;
-
- if (size > BYTES_IN_KILOBYTES) {
- fileSize = size / BYTES_IN_KILOBYTES;
- if (fileSize > BYTES_IN_KILOBYTES) {
- fileSize = fileSize / BYTES_IN_KILOBYTES;
- if (fileSize > BYTES_IN_KILOBYTES) {
- fileSize = fileSize / BYTES_IN_KILOBYTES;
- suffix = GIGABYTES;
- } else {
- suffix = MEGABYTES;
- }
- }
- }
- return dec.format(fileSize) + suffix;
- }
-
- /**
- * Get the Intent for selecting content to be used in an Intent Chooser.
- *
- * @return The intent for opening a file with Intent.createChooser()
- */
- public static Intent createGetContentIntent() {
- // Implicitly allow the user to select a particular kind of data
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- // The MIME data type filter
- intent.setType("*/*");
- // Only return URIs that can be opened with ContentResolver
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- return intent;
- }
-
-
- /**
- * Creates View intent for given file
- *
- * @param file
- * @return The intent for viewing file
- */
- public static Intent getViewIntent(Context context, File file) {
- //Uri uri = Uri.fromFile(file);
- Uri uri = FileProvider.getUriForFile(context, AUTHORITY, file);
- Intent intent = new Intent(Intent.ACTION_VIEW);
- String url = file.toString();
- if (url.contains(".doc") || url.contains(".docx")) {
- // Word document
- intent.setDataAndType(uri, "application/msword");
- } else if (url.contains(".pdf")) {
- // PDF file
- intent.setDataAndType(uri, "application/pdf");
- } else if (url.contains(".ppt") || url.contains(".pptx")) {
- // Powerpoint file
- intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
- } else if (url.contains(".xls") || url.contains(".xlsx")) {
- // Excel file
- intent.setDataAndType(uri, "application/vnd.ms-excel");
- } else if (url.contains(".zip") || url.contains(".rar")) {
- // WAV audio file
- intent.setDataAndType(uri, "application/x-wav");
- } else if (url.contains(".rtf")) {
- // RTF file
- intent.setDataAndType(uri, "application/rtf");
- } else if (url.contains(".wav") || url.contains(".mp3")) {
- // WAV audio file
- intent.setDataAndType(uri, "audio/x-wav");
- } else if (url.contains(".gif")) {
- // GIF file
- intent.setDataAndType(uri, "image/gif");
- } else if (url.contains(".jpg") || url.contains(".jpeg") || url.contains(".png")) {
- // JPG file
- intent.setDataAndType(uri, "image/jpeg");
- } else if (url.contains(".txt")) {
- // Text file
- intent.setDataAndType(uri, "text/plain");
- } else if (url.contains(".3gp") || url.contains(".mpg") || url.contains(".mpeg") ||
- url.contains(".mpe") || url.contains(".mp4") || url.contains(".avi")) {
- // Video files
- intent.setDataAndType(uri, "video/*");
- } else {
- intent.setDataAndType(uri, "*/*");
- }
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- return intent;
- }
-
- public static File getDownloadsDir() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
- }
-
- public static File getDocumentCacheDir(@NonNull Context context) {
- File dir = new File(context.getCacheDir(), DOCUMENTS_DIR);
- if (!dir.exists()) {
- dir.mkdirs();
- }
- logDir(context.getCacheDir());
- logDir(dir);
-
- return dir;
- }
-
- private static void logDir(File dir) {
- if (!DEBUG) return;
- Log.d(TAG, "Dir=" + dir);
- File[] files = dir.listFiles();
- for (File file : files) {
- Log.d(TAG, "File=" + file.getPath());
- }
- }
-
- @Nullable
- public static File generateFileName(@Nullable String name, File directory) {
- if (name == null) {
- return null;
- }
-
- File file = new File(directory, name);
-
- if (file.exists()) {
- String fileName = name;
- String extension = "";
- int dotIndex = name.lastIndexOf('.');
- if (dotIndex > 0) {
- fileName = name.substring(0, dotIndex);
- extension = name.substring(dotIndex);
- }
-
- int index = 0;
-
- while (file.exists()) {
- index++;
- name = fileName + '(' + index + ')' + extension;
- file = new File(directory, name);
- }
- }
-
- try {
- if (!file.createNewFile()) {
- return null;
- }
- } catch (IOException e) {
- Log.w(TAG, e);
- return null;
- }
-
- logDir(directory);
-
- return file;
- }
-
- /**
- * Writes response body to disk
- *
- * @param body ResponseBody
- * @param path file path
- * @return File
- */
- public static File writeResponseBodyToDisk(ResponseBody body, String path) {
- try {
- File target = new File(path);
-
- InputStream inputStream = null;
- OutputStream outputStream = null;
-
- try {
- byte[] fileReader = new byte[4096];
-
- inputStream = body.byteStream();
- outputStream = new FileOutputStream(target);
-
- while (true) {
- int read = inputStream.read(fileReader);
-
- if (read == -1) {
- break;
- }
-
- outputStream.write(fileReader, 0, read);
- }
-
- outputStream.flush();
-
- return target;
- } catch (IOException e) {
- return null;
- } finally {
- if (inputStream != null) {
- inputStream.close();
- }
-
- if (outputStream != null) {
- outputStream.close();
- }
- }
- } catch (IOException e) {
- return null;
- }
- }
-
- private static void saveFileFromUri(Context context, Uri uri, String destinationPath) {
- InputStream is = null;
- BufferedOutputStream bos = null;
- try {
- is = context.getContentResolver().openInputStream(uri);
- bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false));
- byte[] buf = new byte[1024];
- is.read(buf);
- do {
- bos.write(buf);
- } while (is.read(buf) != -1);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (is != null) is.close();
- if (bos != null) bos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
- public static File getFileFromDownloads(@NonNull Context context, Uri uri) {
- String fileName = getFileName(context, uri);
- String filePath = getDownloadsDir().getAbsolutePath() + "/" + fileName;
- return new File(filePath);
- }
-
- public static byte[] readBytesFromFile(String filePath) {
-
- FileInputStream fileInputStream = null;
- byte[] bytesArray = null;
-
- try {
-
- File file = new File(filePath);
- bytesArray = new byte[(int) file.length()];
-
- //read file into bytes[]
- fileInputStream = new FileInputStream(file);
- fileInputStream.read(bytesArray);
-
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (fileInputStream != null) {
- try {
- fileInputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- }
-
- return bytesArray;
-
- }
-
- public static File createTempImageFile(Context context, String fileName) throws IOException {
- // Create an image file name
- File storageDir = new File(context.getCacheDir(), DOCUMENTS_DIR);
- return File.createTempFile(fileName, ".jpg", storageDir);
- }
-
- public static String getFileName(@NonNull Context context, Uri uri) {
- String mimeType = context.getContentResolver().getType(uri);
- String filename = null;
-
- if (mimeType == null && context != null) {
- String path = getPath(context, uri);
- if (path == null) {
- filename = getName(uri.toString());
- } else {
- File file = new File(path);
- filename = file.getName();
- }
- } else {
- Cursor returnCursor = context.getContentResolver().query(uri, null,
- null, null, null);
- if (returnCursor != null) {
- int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
- returnCursor.moveToFirst();
- filename = returnCursor.getString(nameIndex);
- returnCursor.close();
- }
- }
-
- return filename;
- }
-
- private static String getName(String filename) {
- if (filename == null) {
- return null;
- }
- int index = filename.lastIndexOf('/');
- return filename.substring(index + 1);
- }
-
- public static void copy(File src, File dst) throws IOException {
- try (InputStream in = new FileInputStream(src)) {
- try (OutputStream out = new FileOutputStream(dst)) {
- // Transfer bytes from in to out
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
- }
- }
- }
-
public static void trimCache(Context context) {
try {
File dir = context.getCacheDir();
@@ -772,4 +50,13 @@ private static boolean deleteDir(File dir) {
// The directory is now empty so delete it
return dir.delete();
}
+
+ private static void logDir(File dir) {
+ if (!DEBUG) return;
+ Log.d(TAG, "Dir=" + dir);
+ File[] files = dir.listFiles();
+ for (File file : files) {
+ Log.d(TAG, "File=" + file.getPath());
+ }
+ }
}