diff --git a/app/build.gradle b/app/build.gradle index d0822d57..15601dc9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,8 +59,8 @@ android { applicationId "com.amaze.fileutilities" minSdk 19 targetSdk 31 - versionCode 75 - versionName "1.75" + versionCode 76 + versionName "1.76" multiDexEnabled true vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b38fa4e9..a9e56140 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,6 +24,7 @@ tools:ignore="ProtectedPermissions" /> + AudioPlayerInterfaceHandlerViewModel.WAVEFORM_THRESHOLD_BYTES) { viewModel.forceShowSeekbar = true } diff --git a/app/src/main/java/com/amaze/fileutilities/audio_player/IAudioPlayerInterfaceHandler.kt b/app/src/main/java/com/amaze/fileutilities/audio_player/IAudioPlayerInterfaceHandler.kt index f1af9381..c1139377 100644 --- a/app/src/main/java/com/amaze/fileutilities/audio_player/IAudioPlayerInterfaceHandler.kt +++ b/app/src/main/java/com/amaze/fileutilities/audio_player/IAudioPlayerInterfaceHandler.kt @@ -442,7 +442,7 @@ interface IAudioPlayerInterfaceHandler : OnPlaybackInfoUpdate, LifecycleOwner { getContextWeakRef().get()?.let { context -> val file = progressHandler.audioPlaybackInfo - .audioModel.getUri().getFileFromUri() + .audioModel.getUri().getFileFromUri(context) if (file != null) { lifecycleScope.launch { try { @@ -502,7 +502,7 @@ interface IAudioPlayerInterfaceHandler : OnPlaybackInfoUpdate, LifecycleOwner { context -> if (context != null) { val file = audioService?.getAudioProgressHandlerCallback()?.audioPlaybackInfo - ?.audioModel?.getUri()?.getFileFromUri() + ?.audioModel?.getUri()?.getFileFromUri(context) if (file != null) { lifecycleScope.launch { try { diff --git a/app/src/main/java/com/amaze/fileutilities/docx_viewer/DocxModel.kt b/app/src/main/java/com/amaze/fileutilities/docx_viewer/DocxModel.kt index ad5c4287..933a211b 100644 --- a/app/src/main/java/com/amaze/fileutilities/docx_viewer/DocxModel.kt +++ b/app/src/main/java/com/amaze/fileutilities/docx_viewer/DocxModel.kt @@ -42,7 +42,7 @@ data class LocalDocxModel( } override fun getName(context: Context): String { - uri.getFileFromUri()?.run { + uri.getFileFromUri(context)?.run { return this.name } uri.path?.run { diff --git a/app/src/main/java/com/amaze/fileutilities/epub_viewer/EpubViewerActivity.kt b/app/src/main/java/com/amaze/fileutilities/epub_viewer/EpubViewerActivity.kt index 41c02a0f..cb509cbd 100644 --- a/app/src/main/java/com/amaze/fileutilities/epub_viewer/EpubViewerActivity.kt +++ b/app/src/main/java/com/amaze/fileutilities/epub_viewer/EpubViewerActivity.kt @@ -59,7 +59,7 @@ class EpubViewerActivity : PermissionsActivity() { "and mimetype $mimeType" ) epubModel = LocalEpubModel(uri = epubUri, mimeType = mimeType) - val filePathFromUri = epubUri.getFileFromUri() + val filePathFromUri = epubUri.getFileFromUri(this) if (filePathFromUri != null) { val config: Config = Config() .setAllowedDirection(Config.AllowedDirection.ONLY_HORIZONTAL) diff --git a/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseFragment.kt b/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseFragment.kt index f72a604d..8cef7f17 100644 --- a/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseFragment.kt +++ b/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseFragment.kt @@ -24,6 +24,7 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Build import android.os.Bundle +import android.os.Handler import android.provider.Settings import android.view.LayoutInflater import android.view.View @@ -248,8 +249,12 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { junkFilesPreview.invalidateProgress(true, null) junkFiles?.let { junkFilesPreview.invalidateProgress(false, null) - junkFilesPreview.loadPreviews(junkFiles) { - cleanButtonClick(it) { + junkFilesPreview.loadSummaryTextPreview( + if (it.first.isEmpty()) + null else it.second, + null + ) { + cleanButtonClick(it.first) { filesViewModel.junkFilesLiveData = null thread { installedAppsDao.deleteAll() @@ -279,7 +284,10 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { analyseViewModel.getClutteredVideos(mediaFilePair.second) .observe(viewLifecycleOwner) { clutteredVideosInfo -> clutteredVideosInfo?.let { - clutteredVideoPreview.invalidateProgress(false, null) + clutteredVideoPreview.invalidateProgress( + false, + null + ) clutteredVideoPreview.loadPreviews(clutteredVideosInfo) { cleanButtonClick(it) { analyseViewModel.clutteredVideosLiveData = null @@ -291,7 +299,10 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { .observe(viewLifecycleOwner) { largeVideosList -> largeVideosList?.let { - largeVideoPreview.invalidateProgress(false, null) + largeVideoPreview.invalidateProgress( + false, + null + ) largeVideoPreview.loadPreviews(largeVideosList) { cleanButtonClick(it) { analyseViewModel.largeVideosLiveData = null @@ -308,7 +319,10 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { largeDownloads -> largeDownloadPreview.invalidateProgress(true, null) largeDownloads?.let { - largeDownloadPreview.invalidateProgress(false, null) + largeDownloadPreview.invalidateProgress( + false, + null + ) largeDownloadPreview.loadPreviews(largeDownloads) { cleanButtonClick(it) { filesViewModel.largeDownloadsLiveData = null @@ -388,6 +402,32 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { } } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + memoryUsagePreview.visibility = View.VISIBLE + filesViewModel.getMemoryInfo().observe(viewLifecycleOwner) { + memoryUsage -> + memoryUsagePreview.invalidateProgress(true, null) + memoryUsage?.let { + memoryUsagePreview.invalidateProgress(false, null) + memoryUsagePreview.loadSummaryTextPreview(it, { + filesViewModel.memoryInfoLiveData = null + reloadFragment() + }, { + filesViewModel.killBackgroundProcesses( + requireContext() + .packageManager + ) { + requireActivity().runOnUiThread { + requireContext() + .showToastOnBottom(getString(R.string.ram_usage_clear)) + filesViewModel.memoryInfoLiveData = null + reloadFragment() + } + } + }) + } + } + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { unusedAppsPreview.visibility = View.VISIBLE if (!isUsageStatsPermissionGranted()) { @@ -434,7 +474,10 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { mediaFileInfoList -> mostUsedAppsPreview.invalidateProgress(true, null) mediaFileInfoList?.let { - mostUsedAppsPreview.invalidateProgress(false, null) + mostUsedAppsPreview.invalidateProgress( + false, + null + ) mostUsedAppsPreview.loadPreviews(mediaFileInfoList) { cleanButtonClick(it) { filesViewModel.mostUsedAppsLiveData = null @@ -446,7 +489,10 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { mediaFileInfoList -> leastUsedAppsPreview.invalidateProgress(true, null) mediaFileInfoList?.let { - leastUsedAppsPreview.invalidateProgress(false, null) + leastUsedAppsPreview.invalidateProgress( + false, + null + ) leastUsedAppsPreview.loadPreviews(mediaFileInfoList) { cleanButtonClick(it) { filesViewModel.leastUsedAppsLiveData = null @@ -521,6 +567,12 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { } } } + if (analyseViewModel.fragmentScrollPosition != null) { + Handler().postDelayed({ + analyseScrollView.scrollY = analyseViewModel.fragmentScrollPosition!! + analyseViewModel.fragmentScrollPosition = null + }, 1000) + } } return root } @@ -579,6 +631,7 @@ class AnalyseFragment : AbstractMediaFileInfoOperationsFragment() { } private fun reloadFragment() { + analyseViewModel.fragmentScrollPosition = binding.analyseScrollView.scrollY val navController = NavHostFragment.findNavController(this) navController.popBackStack() navController.navigate(R.id.navigation_analyse) diff --git a/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseViewModel.kt b/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseViewModel.kt index 315a4abf..2634e67f 100644 --- a/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseViewModel.kt +++ b/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalyseViewModel.kt @@ -60,6 +60,7 @@ class AnalyseViewModel : ViewModel() { var distractedImagesLiveData: MutableLiveData?>? = null var selfieImagesLiveData: MutableLiveData?>? = null var groupPicImagesLiveData: MutableLiveData?>? = null + var fragmentScrollPosition: Int? = null fun getBlurImages(dao: BlurAnalysisDao): LiveData?> { if (blurImagesLiveData == null) { diff --git a/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalysisTypeView.kt b/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalysisTypeView.kt index 11456fd6..d1fd43f1 100644 --- a/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalysisTypeView.kt +++ b/app/src/main/java/com/amaze/fileutilities/home_page/ui/analyse/AnalysisTypeView.kt @@ -27,6 +27,7 @@ import android.view.LayoutInflater import android.view.View import android.widget.Button import android.widget.FrameLayout +import android.widget.HorizontalScrollView import android.widget.ImageView import android.widget.LinearLayout import android.widget.ProgressBar @@ -56,9 +57,14 @@ class AnalysisTypeView(context: Context, attrs: AttributeSet?) : LinearLayout(co private val loadingHorizontalScroll: ProgressBar private val cancelLoadingView: ImageView private val requirePermissionsParent: LinearLayout + private val requirePermissionsTitle: TextView private val refreshParent: LinearLayout private val grantPermissionButton: Button private val refreshButton: Button + private val requirePermissionScroll: HorizontalScrollView + private val summaryViewParent: LinearLayout + private val summaryTextView: TextView + private val summaryViewButton: Button private var showPreview = false companion object { @@ -81,8 +87,14 @@ class AnalysisTypeView(context: Context, attrs: AttributeSet?) : LinearLayout(co cancelLoadingView = loadingProgressParent.findViewById(R.id.cancel_loading_button) loadingHorizontalScroll = imagesListScroll.findViewById(R.id.scroll_progress) requirePermissionsParent = imagesListParent.findViewById(R.id.require_permission_parent) + requirePermissionsTitle = requirePermissionsParent + .findViewById(R.id.require_permission_title) refreshParent = imagesListParent.findViewById(R.id.refresh_parent) grantPermissionButton = requirePermissionsParent.findViewById(R.id.grant_button) + requirePermissionScroll = imagesListScroll.findViewById(R.id.require_permission_scroll) + summaryViewParent = imagesListScroll.findViewById(R.id.summary_view_parent) + summaryTextView = imagesListScroll.findViewById(R.id.summary_view_title) + summaryViewButton = imagesListScroll.findViewById(R.id.summary_view_button) refreshButton = refreshParent.findViewById(R.id.refresh_button) val a = context.obtainStyledAttributes( @@ -179,6 +191,31 @@ class AnalysisTypeView(context: Context, attrs: AttributeSet?) : LinearLayout(co } } + fun loadSummaryTextPreview( + text: String?, + refreshCallback: (() -> Unit)?, + cleanButtonClick: () -> Unit + ) { + if (text.isNullOrEmpty()) { + hideFade(300) + } + loadingHorizontalScroll.visibility = View.GONE + requirePermissionScroll.visibility = View.GONE + if (showPreview) { + summaryViewParent.visibility = View.VISIBLE + summaryTextView.text = text + if (refreshCallback != null) { + summaryViewButton.visibility = View.VISIBLE + summaryViewButton.setOnClickListener { + refreshCallback.invoke() + } + } + } + cleanButton.setOnClickListener { + cleanButtonClick.invoke() + } + } + private fun getImageView(mediaFileInfo: MediaFileInfo): ImageView { val imageView = ImageView(context) imageView.setOnClickListener { diff --git a/app/src/main/java/com/amaze/fileutilities/home_page/ui/files/FilesViewModel.kt b/app/src/main/java/com/amaze/fileutilities/home_page/ui/files/FilesViewModel.kt index ccda689f..e8beb180 100644 --- a/app/src/main/java/com/amaze/fileutilities/home_page/ui/files/FilesViewModel.kt +++ b/app/src/main/java/com/amaze/fileutilities/home_page/ui/files/FilesViewModel.kt @@ -20,7 +20,9 @@ package com.amaze.fileutilities.home_page.ui.files +import android.app.ActivityManager import android.app.Application +import android.content.Context.ACTIVITY_SERVICE import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager @@ -28,6 +30,7 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.provider.Settings +import android.text.format.Formatter import androidx.annotation.RequiresApi import androidx.core.content.FileProvider import androidx.core.graphics.drawable.toBitmap @@ -111,7 +114,7 @@ class FilesViewModel(val applicationContext: Application) : var largeAppsLiveData: MutableLiveData?>? = null var newlyInstalledAppsLiveData: MutableLiveData?>? = null var recentlyUpdatedAppsLiveData: MutableLiveData?>? = null - var junkFilesLiveData: MutableLiveData?>? = null + var junkFilesLiveData: MutableLiveData, String>?>? = null var apksLiveData: MutableLiveData?>? = null var gamesInstalledLiveData: MutableLiveData?>? = null var largeFilesMutableLiveData: MutableLiveData?>? = null @@ -121,6 +124,7 @@ class FilesViewModel(val applicationContext: Application) : var oldDownloadsLiveData: MutableLiveData?>? = null var oldRecordingsLiveData: MutableLiveData?>? = null var oldScreenshotsLiveData: MutableLiveData?>? = null + var memoryInfoLiveData: MutableLiveData? = null var allMediaFilesPair: ArrayList? = null private var usedVideosSummaryTransformations: LiveData?> { + fun getJunkFilesLiveData(): LiveData, String>?> { if (junkFilesLiveData == null) { junkFilesLiveData = MutableLiveData() junkFilesLiveData?.value = null @@ -1301,7 +1305,16 @@ class FilesViewModel(val applicationContext: Application) : ) } } - junkFilesLiveData?.postValue(result) + var size = 0L + result.forEach { + size += it.longSize + } + junkFilesLiveData?.postValue( + Pair( + result, + Formatter.formatFileSize(applicationContext, size) + ) + ) } } @@ -1499,20 +1512,73 @@ class FilesViewModel(val applicationContext: Application) : } } + @RequiresApi(Build.VERSION_CODES.M) + fun getMemoryInfo(): LiveData { + if (memoryInfoLiveData == null) { + memoryInfoLiveData = MutableLiveData() + memoryInfoLiveData?.value = null + processMemoryUsage() + } + return memoryInfoLiveData!! + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun processMemoryUsage() { + viewModelScope.launch(Dispatchers.IO) { + val activityManager = applicationContext + .getSystemService(ACTIVITY_SERVICE) as ActivityManager + val memInfo = ActivityManager.MemoryInfo() + activityManager.getMemoryInfo(memInfo) + val totalMemory = memInfo.totalMem + val availMemory = memInfo.availMem + val usageSummary = applicationContext.resources.getString( + R.string.ram_usage_title, + String.format("%s", Formatter.formatFileSize(applicationContext, availMemory)), + String.format("%s", Formatter.formatFileSize(applicationContext, totalMemory)) + ) + memoryInfoLiveData?.postValue(usageSummary) + } + } + + fun killBackgroundProcesses(packageManager: PackageManager, callback: () -> Unit) { + viewModelScope.launch(Dispatchers.IO) { + loadAllInstalledApps(packageManager) + allApps.get()?.filter { + it.first.packageName != applicationContext.packageName + }?.forEach { + val activityManager = applicationContext + .getSystemService(ACTIVITY_SERVICE) as ActivityManager + activityManager.killBackgroundProcesses(it.first.packageName) + } + callback.invoke() + } + } + private fun loadAllInstalledApps(packageManager: PackageManager) { if (allApps.get() == null) { val apps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA) allApps.set( apps.map { - var info: PackageInfo? - var androidInfo: PackageInfo? = null - try { - info = packageManager.getPackageInfo( + val info: PackageInfo? = try { + packageManager.getPackageInfo( it.packageName, if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) PackageManager.GET_SIGNATURES else PackageManager.GET_SIGNING_CERTIFICATES ) + } catch (e: PackageManager.NameNotFoundException) { + log.warn( + "failed to find package name {} while loading apps list", + it.packageName, + e + ) + null + } + + Pair(it, info) + }.filter { + val androidInfo: PackageInfo? + try { androidInfo = packageManager.getPackageInfo( "android", @@ -1520,35 +1586,37 @@ class FilesViewModel(val applicationContext: Application) : PackageManager.GET_SIGNATURES else PackageManager.GET_SIGNING_CERTIFICATES ) + !Utils.isAppInSystemPartition(it.first) && ( + it.second == null || + ( + !Utils.isSignedBySystem(it.second, androidInfo) && + !it.second!!.packageName + .equals(applicationContext.packageName) + ) + ) } catch (e: PackageManager.NameNotFoundException) { log.warn( "failed to find package name {} while loading apps list", - it.packageName, + it.first.packageName, e ) - info = null + true } - !Utils.isAppInSystemPartition(it) && - ( - info == null || - ( - !Utils.isSignedBySystem(info, androidInfo) && - !info.packageName.equals(applicationContext.packageName) - ) - ) - Pair(it, info) } ) - insertInstalledApps(apps) + insertInstalledApps() } } - private fun insertInstalledApps(infoList: List) { - val installedApps = infoList.map { - InstalledApps(it.packageName, listOf(it.sourceDir, it.dataDir)) + private fun insertInstalledApps() { + allApps.get()?.let { + infoListPair -> + val installedApps = infoListPair.map { + InstalledApps(it.first.packageName, listOf(it.first.sourceDir, it.first.dataDir)) + } + val dao = AppDatabase.getInstance(applicationContext).installedAppsDao() + dao.insert(installedApps) } - val dao = AppDatabase.getInstance(applicationContext).installedAppsDao() - dao.insert(installedApps) } /** diff --git a/app/src/main/java/com/amaze/fileutilities/image_viewer/ImageViewerFragment.kt b/app/src/main/java/com/amaze/fileutilities/image_viewer/ImageViewerFragment.kt index 4950266a..348a2e05 100644 --- a/app/src/main/java/com/amaze/fileutilities/image_viewer/ImageViewerFragment.kt +++ b/app/src/main/java/com/amaze/fileutilities/image_viewer/ImageViewerFragment.kt @@ -140,7 +140,7 @@ class ImageViewerFragment : AbstractMediaFragment() { customToolbar.title.text = DocumentFile.fromSingleUri( requireContext(), quickViewType!!.uri - )?.name ?: quickViewType.uri.getFileFromUri()?.name + )?.name ?: quickViewType.uri.getFileFromUri(requireContext())?.name customToolbar.backButton.setOnClickListener { requireActivity().onBackPressed() } @@ -167,7 +167,7 @@ class ImageViewerFragment : AbstractMediaFragment() { private fun setupPropertiesSheet(quickViewType: LocalImageModel) { quickViewType.let { - val file = it.uri.getFileFromUri() + val file = it.uri.getFileFromUri(requireContext()) file?.let { file -> _binding?.run { @@ -458,7 +458,7 @@ class ImageViewerFragment : AbstractMediaFragment() { resources.getString(R.string.delete) ) { localImageModel!!.uri - .getFileFromUri()?.let { + .getFileFromUri(requireContext())?.let { file -> val toDelete = Collections.singletonList( MediaFileInfo.fromFile( diff --git a/app/src/main/java/com/amaze/fileutilities/pdf_viewer/PdfViewerActivity.kt b/app/src/main/java/com/amaze/fileutilities/pdf_viewer/PdfViewerActivity.kt index 9aae00d8..c0cd813d 100644 --- a/app/src/main/java/com/amaze/fileutilities/pdf_viewer/PdfViewerActivity.kt +++ b/app/src/main/java/com/amaze/fileutilities/pdf_viewer/PdfViewerActivity.kt @@ -39,6 +39,7 @@ import com.amaze.fileutilities.databinding.PdfViewerActivityBinding import com.amaze.fileutilities.home_page.ui.files.FilesViewModel import com.amaze.fileutilities.home_page.ui.files.MediaFileInfo import com.amaze.fileutilities.utilis.dp +import com.amaze.fileutilities.utilis.getContentName import com.amaze.fileutilities.utilis.getFileFromUri import com.amaze.fileutilities.utilis.hideFade import com.amaze.fileutilities.utilis.share.showShareDialog @@ -108,10 +109,8 @@ class PdfViewerActivity : override fun loadComplete(nbPages: Int) { val meta: PdfDocument.Meta = viewBinding.pdfView.documentMeta - viewModel.pdfFileName = if (meta.title.isEmpty()) { - pdfModel.uri.getFileFromUri()?.name - } else { - meta.title + viewModel.pdfFileName = meta.title.ifEmpty { + pdfModel.uri.getFileFromUri(this)?.name } viewBinding.pageNumber.text = String.format( "%s / %s", viewModel.pageNumber + 1, @@ -121,7 +120,10 @@ class PdfViewerActivity : switchView() } viewBinding.customToolbar.setBackButtonClickListener { finish() } - viewBinding.customToolbar.setTitle(viewModel.pdfFileName ?: "pdf") + viewBinding.customToolbar.setTitle( + viewModel.pdfFileName + ?: pdfModel.uri.getContentName(contentResolver) ?: "pdf" + ) viewBinding.customToolbar.setOverflowPopup(R.menu.pdf_activity) { item -> when (item!!.itemId) { R.id.info -> { @@ -135,7 +137,7 @@ class PdfViewerActivity : } R.id.share -> { var processed = false - pdfModel.uri.getFileFromUri().let { + pdfModel.uri.getFileFromUri(this).let { file -> if (file == null) { showToastInCenter(getString(R.string.failed_to_share)) diff --git a/app/src/main/java/com/amaze/fileutilities/utilis/Extensions.kt b/app/src/main/java/com/amaze/fileutilities/utilis/Extensions.kt index dcac91ef..e10afebd 100644 --- a/app/src/main/java/com/amaze/fileutilities/utilis/Extensions.kt +++ b/app/src/main/java/com/amaze/fileutilities/utilis/Extensions.kt @@ -20,6 +20,7 @@ package com.amaze.fileutilities.utilis +import android.content.ContentResolver import android.content.Context import android.content.SharedPreferences import android.content.res.Resources @@ -28,6 +29,8 @@ import android.net.ConnectivityManager import android.net.Uri import android.os.Build import android.os.Environment +import android.os.ParcelFileDescriptor +import android.provider.MediaStore import android.util.DisplayMetrics import android.view.Gravity import android.view.View @@ -58,6 +61,9 @@ import kotlinx.coroutines.withContext import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.File +import java.io.FileDescriptor +import java.io.FileInputStream +import java.io.FileOutputStream var log: Logger = LoggerFactory.getLogger(Utils::class.java) @@ -94,6 +100,70 @@ fun Uri.getSiblingUriFiles(): ArrayList? { return null } +fun Uri.getFileFromUri(context: Context): File? { + if (this == Uri.EMPTY) { + return null + } + var songFile: File? = getFileFromUri() + if (songFile == null) { + songFile = getContentResolverFilePathFromUri(context, this)?.let { + filePath -> + File(filePath) + } + if (songFile == null) { + var parcelFileDescriptor: ParcelFileDescriptor? = null + var outputStream: FileOutputStream? = null + try { + parcelFileDescriptor = context.contentResolver.openFileDescriptor( + this, + "r" + ) + parcelFileDescriptor?.let { + val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor + val fis = FileInputStream(fileDescriptor) + val outputFile = File( + context.cacheDir, + getContentName(context.contentResolver) ?: "sharedFile" + ) + outputStream = outputFile.outputStream() + outputStream?.let { + fis.copyTo(it) + } + songFile = outputFile + } + } finally { + parcelFileDescriptor?.close() + outputStream?.close() + } + } + } + return songFile +} + +fun Uri.getContentName(resolver: ContentResolver): String? { + var cursor: Cursor? = null + try { + cursor = + resolver.query( + this, arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), + null, + null, null + ) + cursor!!.moveToFirst() + val nameIndex = cursor.getColumnIndex(cursor.columnNames[0]) + return if (nameIndex >= 0) { + cursor.getString(nameIndex) + } else { + null + } + } catch (e: Exception) { + log.warn("failed to load name for uri {}", this) + return null + } finally { + cursor?.close() + } +} + fun Uri.getFileFromUri(): File? { if (this == Uri.EMPTY) { return null @@ -116,7 +186,7 @@ fun Uri.getFileFromUri(): File? { ) ) if (songFile == null || !songFile.exists()) { - songFile = File(this.path) + songFile = this.path?.let { File(it) } } } if (songFile == null || !songFile.exists()) { @@ -131,17 +201,18 @@ fun File.getUriFromFile(context: Context): Uri { private fun getContentResolverFilePathFromUri(context: Context, uri: Uri): String? { var cursor: Cursor? = null - val column = "_data" - val projection = arrayOf( - column - ) try { - cursor = context.contentResolver.query( - uri, projection, null, null, - null - ) + val projection = arrayOf(MediaStore.Files.FileColumns.DATA) + cursor = context + .contentResolver + .query( + uri, + projection, null, + null, + null + ) if (cursor != null && cursor.moveToFirst()) { - val columnIndex = cursor.getColumnIndexOrThrow(column) + val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA) return cursor.getString(columnIndex) } } catch (e: Exception) { diff --git a/app/src/main/java/com/amaze/fileutilities/utilis/Utils.kt b/app/src/main/java/com/amaze/fileutilities/utilis/Utils.kt index ec87e91e..40592c2f 100644 --- a/app/src/main/java/com/amaze/fileutilities/utilis/Utils.kt +++ b/app/src/main/java/com/amaze/fileutilities/utilis/Utils.kt @@ -108,7 +108,8 @@ class Utils { var log: Logger = LoggerFactory.getLogger(Utils::class.java) const val URL_PRIVACY_POLICY = "https://teamamaze.xyz/privacy-policy-utilities" - const val URL_LICENSE_AGREEMENT = "https://teamamaze.xyz/license-agreement-utilities" + const val URL_LICENSE_AGREEMENT = + "https://github.com/TeamAmaze/AmazeFileUtilities/blob/main/LICENSE.txt" const val URL_GITHUB_ISSUES = "https://github.com/TeamAmaze/AmazeFileUtilities-Issue-Tracker/issues" const val AMAZE_FILE_MANAGER_MAIN = "com.amaze.filemanager.ui.activities.MainActivity" diff --git a/app/src/main/java/com/amaze/fileutilities/video_player/BaseVideoPlayerActivity.kt b/app/src/main/java/com/amaze/fileutilities/video_player/BaseVideoPlayerActivity.kt index 20a771dc..a5498727 100644 --- a/app/src/main/java/com/amaze/fileutilities/video_player/BaseVideoPlayerActivity.kt +++ b/app/src/main/java/com/amaze/fileutilities/video_player/BaseVideoPlayerActivity.kt @@ -595,7 +595,9 @@ abstract class BaseVideoPlayerActivity : customToolbar.setBackButtonClickListener { onBackPressed() } - val mediaFile = videoPlayerViewModel?.videoModel?.uri?.getFileFromUri() + val mediaFile = videoPlayerViewModel?.videoModel?.uri?.getFileFromUri( + this@BaseVideoPlayerActivity + ) val fileName = mediaFile?.name customToolbar.setTitle(fileName ?: "") customToolbar.setOverflowPopup(R.menu.video_activity) { item -> @@ -712,7 +714,7 @@ abstract class BaseVideoPlayerActivity : R.id.search_subtitles -> { if (isNetworkAvailable()) { videoPlayerViewModel?.videoModel?.uri?.let { - val mediaFile = it.getFileFromUri() + val mediaFile = it.getFileFromUri(this) mediaFile?.let { file -> player?.pause() @@ -794,7 +796,7 @@ abstract class BaseVideoPlayerActivity : var dialogMessage = "" val uri = videoPlayerViewModel?.videoModel?.uri uri?.let { - val file = uri.getFileFromUri() + val file = uri.getFileFromUri(this) file?.let { dialogMessage += "${resources.getString(R.string.file)}\n---\n" dialogMessage += "${resources.getString(R.string.name)}: ${file.name}" + "\n" @@ -1016,7 +1018,7 @@ abstract class BaseVideoPlayerActivity : setMediaItemWithSubtitle(File(videoPlayerViewModel?.subtitleFilePath!!)) } else { val uri = videoPlayerViewModel?.videoModel?.uri - setMediaItemWithSubtitle(uri?.getFileFromUri()) + setMediaItemWithSubtitle(uri?.getFileFromUri(this)) } } diff --git a/app/src/main/play/release-notes/en-US/production.txt b/app/src/main/play/release-notes/en-US/production.txt index 8e6075e5..934bb79f 100644 --- a/app/src/main/play/release-notes/en-US/production.txt +++ b/app/src/main/play/release-notes/en-US/production.txt @@ -1,7 +1,6 @@ Changelog: +- Add analysis to clear background processes +- Add analysis for newly installed / recently updated apps - Add support for playlists in music player - Add Floating Action Button for options menu -- Add new analysis capabilities -- Add group summary in media list screen -- Music player performance fixes - Other bugfixes diff --git a/app/src/main/res/layout/analysis_type_view.xml b/app/src/main/res/layout/analysis_type_view.xml index 8ecb0ec4..353e59a1 100644 --- a/app/src/main/res/layout/analysis_type_view.xml +++ b/app/src/main/res/layout/analysis_type_view.xml @@ -22,6 +22,7 @@ android:layout_gravity="center_horizontal|center_vertical" /> @@ -97,6 +98,35 @@ + + +