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()); + } + } }