How to list PDF files in Android? - android

I have the following code:
public ArrayList<File> getPDFs(File directory) {
ArrayList<File> pdfFileList = new ArrayList<>();
File[] paths = directory.listFiles();
if (!(paths == null)) {
for (File path : paths) {
if (path.isFile() && path.getName().endsWith(".pdf")) {
pdfFileList.add(path);
}
else if (path.isDirectory()) {
getPDFs(path);
}
}
}
return pdfFileList;
}
I also have the READ_EXTERNAL_STORAGE permission in my manifest file. When I call this function (e.g., getPDFs(Environment.getExternalStorageDirectory());) only folders are listed - I can't see any files. I downloaded a sample PDF with Chrome onto the emulator, but it doesn't get listed here. How can I list files (specifically PDF documents) in Android? I'm using Android 11.
Edit: The issue seems to be related to Chrome "owning" the file - my app didn't create the file and therefore cannot read it. I'm not sure how to get around this.
Edit 2: I believe I need to use the Storage Access Framework to access a directory, but again I'm not sure how.

Here is the source code try the following code you can get all pdf files from a mobile directory.
The code is in Kotlin Language hope you will understand. You should add permissions in manifest
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
LoaderManager.kt
class LoaderManager {
companion object {
val handler = CoroutineExceptionHandler { _, exception ->
exception.printStackTrace()
}
val pdfFileList = ArrayList<FileItem>()
fun getFilesLoader(
callback: () -> Unit
) {
GlobalScope.launch(Dispatchers.Main + handler) {
async(Dispatchers.IO + handler) {
getFiles(Environment.getExternalStorageDirectory())
}.await()
callback.invoke()
}
}
private fun getFiles(dir: File) {
try {
val pdfPattern = ".pdf"
val listFile = dir.listFiles()
if (listFile != null && listFile.isNotEmpty()) {
for (file in listFile) {
if (file.isDirectory) {
getFiles(file)
}
}
Arrays.sort(
listFile
) { file: File, t1: File ->
file.lastModified().compareTo(t1.lastModified())
}
for (file in listFile) {
if (file.exists() && file.name.endsWith(pdfPattern)) {
pdfFileList.add(FileItem(file))
}
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
fun isFilesListEmpty() = pdfFileList.isEmpty()
fun clearFilesData() {
pdfFileList.clear()
}
}
}
LoaderViewModel.kt
class LoaderViewModel : ViewModel() {
private val _filesLoad = MutableLiveData<Boolean>()
var isFilesLoaded: LiveData<Boolean> = Transformations.map(_filesLoad) { it }
fun setFilesLoaded(isLoaded: Boolean) {
_filesLoad.value = isLoaded
}
fun getGalleryFiles() {
if (LoaderManager.isFilesListEmpty()) {
LoaderManager.getFilesLoader {
setFilesLoaded(true)
}
} else {
setFilesLoaded(true)
}
}
}
FileItem.kt
data class FileItem(
var pdfFilePath: File
)
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var loaderViewModel: LoaderViewModel
private lateinit var mAdapter: AdapterFilesLoader
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
initViewModel()
createLoaderRecyclerView()
if (SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()){
loaderViewModel.getGalleryFiles()
}else{
showPermissionDialog()
}
} else {
if (checkReadWritePermission()) {
loaderViewModel.getGalleryFiles()
}else{
requestStoragePermission()
}
}
}
private fun initViewModel(){
loaderViewModel = ViewModelProvider(this).get(LoaderViewModel::class.java)
loaderViewModel.isFilesLoaded.observe(this) {
if (it){
binding.loadingProgressBar.visibility = View.GONE
Log.i("PDFTesting","${LoaderManager.pdfFileList.size}")
mAdapter.submitList(LoaderManager.pdfFileList)
if (LoaderManager.isFilesListEmpty()){
binding.noFilesLayout.visibility = View.VISIBLE
}
}else{
binding.loadingProgressBar.visibility = View.GONE
binding.noFilesLayout.visibility = View.GONE
}
}
}
private fun createLoaderRecyclerView() {
mAdapter = AdapterFilesLoader()
binding.recyclerview.adapter = mAdapter
binding.recyclerview.layoutManager = GridLayoutManager(this, 3)
mAdapter.setOnItemClickListener(object : OnItemClickListener {
override fun onItemClick(position: Int) {
showMessage(mAdapter.currentList[position].pdfFilePath.absolutePath)
}
})
}
private fun checkReadWritePermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
private fun requestStoragePermission() {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
STORAGE_PERMISSION
)
}
#RequiresApi(Build.VERSION_CODES.R)
private fun requestExternalStorageManager(){
try {
val mIntent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
mIntent.addCategory("android.intent.category.DEFAULT")
mIntent.data = Uri.parse(String.format("package:%s", packageName))
openActivityForResult(mIntent)
} catch (e: Exception) {
val mIntent = Intent()
mIntent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
openActivityForResult(mIntent)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (STORAGE_PERMISSION==requestCode){
if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
loaderViewModel.getGalleryFiles()
} else {
loaderViewModel.setFilesLoaded(false)
showMessage("Permission Denied!")
}
}
}
private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()){
loaderViewModel.getGalleryFiles()
}else{
showPermissionDialog()
}
}
}
private fun openActivityForResult(mIntent:Intent) {
resultLauncher.launch(mIntent)
}
private fun showMessage(message:String){
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
LoaderManager.clearFilesData()
}
private fun showPermissionDialog() {
DialogUtils.permissionDialog(
this,
object : OnDialogPermissionClickListener {
override fun onDiscardClick() {
loaderViewModel.setFilesLoaded(false)
}
#RequiresApi(Build.VERSION_CODES.R)
override fun onProceedClick() {
requestExternalStorageManager()
}
})
}
}
Here I just want to explain. just download the source code you will get the exact source code. In Android 11 or above you just give the setting permission to read the files

Related

Open the front camera(kotlin)

I have the following code which opens the rear camera in SurfaceView when the activity is opened.
Now I want to know how to change this code to open the front camera instead of the rear camera
and I have given access to the front camera in the manifest.
And I don't want to go to camera using Intent
Thankful
class mirrorActivity : AppCompatActivity(), SurfaceHolder.Callback {
private lateinit var binding: ActivityMirrorBinding
private var surfaceHolder: SurfaceHolder? = null
private var camera: Camera? = null
private val neededPermissions = arrayOf(CAMERA)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMirrorBinding.inflate(layoutInflater)
setContentView(binding.root)
val result = checkPermission()
if (result) {
setupSurfaceHolder()
}
}
private fun checkPermission(): Boolean {
val currentAPIVersion = Build.VERSION.SDK_INT
if (currentAPIVersion >= Build.VERSION_CODES.M) {
val permissionsNotGranted = ArrayList<String>()
for (permission in neededPermissions) {
if (ContextCompat.checkSelfPermission( this, permission)
!=PackageManager.PERMISSION_GRANTED){
permissionsNotGranted.add(permission)
}
}
if (permissionsNotGranted.size > 0) {
var shouldShowAlert = false
for (permission in permissionsNotGranted) {
shouldShowAlert =
ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
}
val arr = arrayOfNulls<String>(permissionsNotGranted.size)
val permissions = permissionsNotGranted.toArray(arr)
if (shouldShowAlert) {
showPermissionAlert(permissions)
} else {
requestPermissions(permissions)
}
return false
}
}
return true
}
private fun showPermissionAlert(permissions: Array<String?>) {
val alertBuilder = AlertDialog.Builder(this)
alertBuilder.setCancelable(true)
alertBuilder.setTitle("Permission Required")
alertBuilder.setMessage("You must grant permission to access camera and external storage to run this application")
alertBuilder.setPositiveButton(R.string.no_data) { _, _ -> requestPermissions(permissions) }
val alert = alertBuilder.create()
alert.show()
}
private fun requestPermissions(permissions: Array<String?>) {
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
REQUEST_CODE -> {
for (result in grantResults) {
if (result == PackageManager.PERMISSION_DENIED) {
//t
binding.showPermissionMsg.visibility = View.VISIBLE
return
}
}
setupSurfaceHolder() } }
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun setupSurfaceHolder() {
surfaceHolder = binding.surfaceView.holder
binding.surfaceView.holder.addCallback(this)
}
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
startCamera()
}
private fun startCamera() {
camera = Camera.open()
camera!!.setDisplayOrientation(90)
try {
camera!!.setPreviewDisplay(surfaceHolder)
camera!!.startPreview()
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun surfaceChanged(surfaceHolder: SurfaceHolder, i: Int, i1: Int, i2: Int) {
resetCamera()
}
private fun resetCamera() {
if (surfaceHolder!!.surface == null) {
return
}
camera!!.stopPreview()
try {
camera!!.setPreviewDisplay(surfaceHolder)
} catch (e: IOException) {
e.printStackTrace()
}
camera!!.startPreview()
}
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
releaseCamera()
}
private fun releaseCamera() {
camera!!.stopPreview()
camera!!.release()
camera = null
}
companion object {
const val REQUEST_CODE = 100
}
}
You can use Camera.CameraInfo.CAMERA_FACING_FRONT for the front camera and Camera.CameraInfo.CAMERA_FACING_BACK for the back camera, you can use them in your code the following :
private int currentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
or
private int currentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
And set it like this :
Camera.open(currentCameraId)
Also, put the whole camera code in the onResume() method

Kotlin Type mismatch: inferred type is Activity? but Context was expected ERROR after upgrading flutter to the 3.0.0

I am using beacon plugin. After I upgraded flutter to the version 3.0.0, it throwed this error:
Class 'BeaconsPlugin' is not abstract and does not implement abstract member public abstract fun onRequestPermissionsResult(p0: Int, p1: Array<(out) String!>, p2: IntArray): Boolean defined in io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
e: C:\Software\HR-Management-Localization\mapping_tool\packages\simple_beacons_flutter\android\src\main\kotlin\com\umair\beacons_plugin\BeaconsPlugin.kt: (66, 32): Type mismatch: inferred type is Activity? but Context was expected
e: C:\Software\HR-Management-Localization\mapping_tool\packages\simple_beacons_flutter\android\src\main\kotlin\com\umair\beacons_plugin\BeaconsPlugin.kt: (428, 5): 'onRequestPermissionsResult' overrides nothing
I've solved it by removing question marks from lines
permissions: Array<out String>?,
grantResults: IntArray?
to this
permissions: Array<out String>,
grantResults: IntArray
in onRequestPermissionsResult method.
And than it throwed a different error: BeaconsPlugin.kt: (66, 32): Type mismatch: inferred type is Activity? but Context was expected
I don't know kotlin therefore unable to solve this.
Here is my whole BeaconsPlugin.kt:
package com.umair.beacons_plugin
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.*
import timber.log.Timber
/** BeaconsPlugin */
class BeaconsPlugin : FlutterPlugin, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
private var context: Context? = null
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
Timber.i("onAttachedToEngine")
messenger = flutterPluginBinding.binaryMessenger
setUpPluginMethods(
flutterPluginBinding.applicationContext,
flutterPluginBinding.binaryMessenger
)
context = flutterPluginBinding.applicationContext
beaconHelper = BeaconHelper(flutterPluginBinding.applicationContext)
context?.let {
BeaconPreferences.init(it)
stopBackgroundService(it)
}
}
companion object {
private val TAG = "BeaconsPlugin"
private var REQUEST_LOCATION_PERMISSIONS = 1890
private var PERMISSION_REQUEST_BACKGROUND_LOCATION = 1891
private var channel: MethodChannel? = null
private var event_channel: EventChannel? = null
private var currentActivity: Activity? = null
private var beaconHelper: BeaconHelper? = null
private var defaultPermissionDialogTitle = "This app needs background location access"
private var defaultPermissionDialogMessage =
"Please grant location access so this app can detect beacons in the background."
#JvmStatic
internal var messenger: BinaryMessenger? = null
#JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
BeaconPreferences.init(registrar.context())
if (beaconHelper == null) {
this.beaconHelper = BeaconHelper(registrar.context())
}
val instance = BeaconsPlugin()
registrar.addRequestPermissionsResultListener(instance)
//requestPermission()
setUpPluginMethods(registrar.activity(), registrar.messenger())
}
#JvmStatic
fun registerWith(messenger: BinaryMessenger, context: Context) {
BeaconPreferences.init(context)
if (beaconHelper == null) {
this.beaconHelper = BeaconHelper(context)
}
val instance = BeaconsPlugin()
//requestPermission()
setUpPluginMethods(context, messenger)
}
#JvmStatic
fun registerWith(messenger: BinaryMessenger, beaconHelper: BeaconHelper, context: Context) {
BeaconPreferences.init(context)
this.beaconHelper = beaconHelper
val instance = BeaconsPlugin()
//requestPermission()
setUpPluginMethods(context, messenger)
}
#JvmStatic
private fun setUpPluginMethods(context: Context, messenger: BinaryMessenger) {
Timber.plant(Timber.DebugTree())
channel = MethodChannel(messenger, "beacons_plugin")
notifyIfPermissionsGranted(context)
channel?.setMethodCallHandler { call, result ->
when {
call.method == "startMonitoring" -> {
stopService = false
callBack?.startScanning()
result.success("Started scanning Beacons.")
}
call.method == "stopMonitoring" -> {
if (runInBackground) {
stopService = true
context.let {
stopBackgroundService(it)
}
}
callBack?.stopMonitoringBeacons()
result.success("Stopped scanning Beacons.")
}
call.method == "addRegion" -> {
callBack?.addRegion(call, result)
}
call.method == "clearRegions" -> {
callBack?.clearRegions(call, result)
}
call.method == "runInBackground" -> {
call.argument<Boolean>("background")?.let {
runInBackground = it
}
result.success("App will run in background? $runInBackground")
}
call.method == "clearDisclosureDialogShowFlag" -> {
call.argument<Boolean>("clearFlag")?.let {
if (it) {
clearPermissionDialogShownFlag()
result.success("clearDisclosureDialogShowFlag: Flag cleared!")
} else {
setPermissionDialogShown()
result.success("clearDisclosureDialogShowFlag: Flag Set!")
}
}
}
call.method == "setDisclosureDialogMessage" -> {
call.argument<String>("title")?.let {
defaultPermissionDialogTitle = it
}
call.argument<String>("message")?.let {
defaultPermissionDialogMessage = it
}
requestPermission()
result.success("Disclosure message Set: $defaultPermissionDialogMessage")
}
call.method == "addBeaconLayoutForAndroid" -> {
call.argument<String>("layout")?.let {
callBack?.addBeaconLayout(it)
result.success("Beacon layout added: $it")
}
}
call.method == "setForegroundScanPeriodForAndroid" -> {
var foregroundScanPeriod = 1100L
var foregroundBetweenScanPeriod = 0L
call.argument<Int>("foregroundScanPeriod")?.let {
if (it > foregroundScanPeriod) {
foregroundScanPeriod = it.toLong()
}
}
call.argument<Int>("foregroundBetweenScanPeriod")?.let {
if (it > foregroundBetweenScanPeriod) {
foregroundBetweenScanPeriod = it.toLong()
}
}
callBack?.setForegroundScanPeriod(
foregroundScanPeriod = foregroundScanPeriod,
foregroundBetweenScanPeriod = foregroundBetweenScanPeriod
)
result.success("setForegroundScanPeriod updated.")
}
call.method == "setBackgroundScanPeriodForAndroid" -> {
var backgroundScanPeriod = 1100L
var backgroundBetweenScanPeriod = 0L
call.argument<Int>("backgroundScanPeriod")?.let {
if (it > backgroundScanPeriod) {
backgroundScanPeriod = it.toLong()
}
}
call.argument<Int>("backgroundBetweenScanPeriod")?.let {
if (it > backgroundBetweenScanPeriod) {
backgroundBetweenScanPeriod = it.toLong()
}
}
callBack?.setBackgroundScanPeriod(
backgroundScanPeriod = backgroundScanPeriod,
backgroundBetweenScanPeriod = backgroundBetweenScanPeriod
)
result.success("setBackgroundScanPeriod updated.")
}
else -> result.notImplemented()
}
}
event_channel = EventChannel(messenger, "beacons_plugin_stream")
event_channel?.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
callBack?.setEventSink(events)
}
override fun onCancel(arguments: Any?) {
}
})
}
#JvmStatic
private fun notifyIfPermissionsGranted(context: Context) {
if (permissionsGranted(context)) {
doIfPermissionsGranted()
}
}
#JvmStatic
fun permissionsGranted(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
#JvmStatic
private fun doIfPermissionsGranted() {
Timber.i("doIfPermissionsGranted")
if (beaconHelper == null) {
return
}
this.callBack = beaconHelper
sendBLEScannerReadyCallback()
}
#JvmStatic
private fun requestPermission() {
if (areBackgroundScanPermissionsGranted()) {
requestLocationPermissions()
} else {
requestBackgroundPermission()
}
}
private fun requestLocationPermissions() {
if (!arePermissionsGranted()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
currentActivity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
REQUEST_LOCATION_PERMISSIONS
)
}
} else {
currentActivity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
),
REQUEST_LOCATION_PERMISSIONS
)
}
}
} else {
doIfPermissionsGranted()
}
} else {
doIfPermissionsGranted()
}
}
#JvmStatic
private fun requestBackgroundPermission() {
if (!isPermissionDialogShown()) {
currentActivity?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//if (it.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
val builder: AlertDialog.Builder =
AlertDialog.Builder(it)
builder.setTitle(defaultPermissionDialogTitle)
builder.setMessage(defaultPermissionDialogMessage)
builder.setPositiveButton("Ok", null)
builder.setOnDismissListener {
setPermissionDialogShown()
requestLocationPermissions()
channel?.invokeMethod("isPermissionDialogShown", "true")
}
builder.show()
//}
}
}
}
}
#JvmStatic
private fun arePermissionsGranted(): Boolean {
currentActivity?.let {
return ContextCompat.checkSelfPermission(
it,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
it,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
return false
}
#JvmStatic
private fun areBackgroundScanPermissionsGranted(): Boolean {
currentActivity?.let {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContextCompat.checkSelfPermission(
it,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED
} else {
return true
}
}
return true
}
#JvmStatic
var runInBackground = false
#JvmStatic
var stopService = false
interface PluginImpl {
fun startScanning()
fun stopMonitoringBeacons()
fun addRegion(call: MethodCall, result: MethodChannel.Result)
fun clearRegions(call: MethodCall, result: MethodChannel.Result)
fun setEventSink(events: EventChannel.EventSink?)
fun addBeaconLayout(layout: String)
fun setForegroundScanPeriod(
foregroundScanPeriod: Long,
foregroundBetweenScanPeriod: Long
)
fun setBackgroundScanPeriod(
backgroundScanPeriod: Long,
backgroundBetweenScanPeriod: Long
)
}
private var callBack: PluginImpl? = null
fun sendBLEScannerReadyCallback() {
channel?.invokeMethod("scannerReady", "")
}
fun startBackgroundService(context: Context) {
if (runInBackground && !stopService) {
val serviceIntent1 = Intent(context, BeaconsDiscoveryService::class.java)
context.startService(serviceIntent1)
}
}
fun stopBackgroundService(context: Context) {
if (runInBackground && !stopService) {
val serviceIntent = Intent(context, BeaconsDiscoveryService::class.java)
context.stopService(serviceIntent)
}
}
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
currentActivity = null
channel?.setMethodCallHandler(null)
event_channel?.setStreamHandler(null)
if (!BeaconsPlugin.runInBackground)
beaconHelper?.stopMonitoringBeacons()
context?.let {
stopBackgroundService(it)
}
context = null
}
override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
currentActivity = activityPluginBinding.activity
BeaconPreferences.init(currentActivity)
activityPluginBinding.addRequestPermissionsResultListener(this)
//requestPermission()
if (arePermissionsGranted()) {
sendBLEScannerReadyCallback()
}
}
override fun onDetachedFromActivityForConfigChanges() {
Timber.i("onDetachedFromActivityForConfigChanges")
}
override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) {
Timber.i("onReattachedToActivityForConfigChanges")
currentActivity = activityPluginBinding.activity
activityPluginBinding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivity() {
Timber.i("onDetachedFromActivity")
currentActivity = null
context?.let {
startBackgroundService(it)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
if (requestCode == REQUEST_LOCATION_PERMISSIONS && grantResults?.isNotEmpty()!! && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
doIfPermissionsGranted()
return true
}
if (requestCode == PERMISSION_REQUEST_BACKGROUND_LOCATION && grantResults?.isNotEmpty()!! && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//setPermissionDialogShown()
//requestPermission()
return true
}
return false
}
}
i have save problem in deferent class and solved by edit this one
#JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
val instance = BackgroundSttPlugin()
registrar.addRequestPermissionsResultListener(instance)
requestRecordPermission()
// this one "registrar.context()" make my problem solved
setUpPluginMethods(registrar.context(), registrar.messenger())
}

How to fetch files inside hidden folder using Media Store API on Android 11

I need to fetch data inside WhatsApp folders on External Storage.
As i am targeting API Level 30 i am no longer able to access WhatsApp folders on External Storage. I have implemented Storage Access Framework and got Android/media folder Uri and Document File. And using listFiles() i am able to list files but with filter() and sortedByDescending() functions it becomes very slow.
What i have tried?
Used Cursor loader with Projection and Selection Arguments but it only
worked for non hidden folders like WhatsApp Images and WhatsApp Videos
It returns empty cursor for hidden folder .Statuses
Tried replacing MediaStore.Video.Media.EXTERNAL_CONTENT_URI with MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
What is required?
List images and videos from .Statuses folder same as i am listing WhatsApp Images using Media Store in HomeActivity.java
Below is my code
In this activity i get permision to Android/media and set all WhatsApp folders URIs for status fetching and other use, but fetched WhatsApp Images with projection and selection from WhatsApp Images folder
class HomeActivity : AppCompatActivity(), InternetListener, PurchasesUpdatedListener,
CoroutineScope {
private val exceptionHandler = CoroutineExceptionHandler { context, exception ->
Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show()
}
private val dataRepository: DataRepository by inject()
val tinyDB: TinyDB by inject()
val REQUEST_CODE = 12123
init {
newNativeAdSetUp = null
}
val sharedViewModel by viewModel<SharedViewModel>()
val viewModel by viewModel<HomeViewModel>()
val handler = CoroutineExceptionHandler { _, exception ->
Log.d("CoroutineException", "$exception handled !")
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job + handler
private lateinit var job: Job
val sdk30PermissionListener = object : PermissionListener {
override fun onPermissionGranted() {
openDocumentTree()
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
handlePermissionsByVersion()
}
private fun handlePermissionsByVersion() {
if (SDK_INT >= Build.VERSION_CODES.R) {
if ((ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
)
== PackageManager.PERMISSION_GRANTED)
) {
//if granted load whatsapp images and some uris setup to viewmodel
loadWhatsAppImages()
if (arePermissionsGranted()) {
if (dataRepository.mrWhatsAppImages == null || dataRepository.mrWhatsAppBusinessImages == null) {
setUpWAURIs()
}
}
} else {
TedPermission.with(this)
.setPermissionListener(sdk30PermissionListener)
.setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
.setPermissions(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
.check()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, #Nullable data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
if (data != null) {
//this is the uri user has provided us
val treeUri: Uri? = data.data
if (treeUri != null) {
sharedViewModel.treeUri = treeUri
val decoded = Uri.decode(treeUri.toString())
Log.i(LOGTAG, "got uri: ${treeUri.toString()}")
// here we should do some checks on the uri, we do not want root uri
// because it will not work on Android 11, or perhaps we have some specific
// folder name that we want, etc
if (Uri.decode(treeUri.toString()).endsWith(":")) {
showWrongFolderSelection()
return
}
if (!decoded.equals(Constants.WHATSAPP_MEDIA_URI_DECODED)) {
showWrongFolderSelection()
return
}
// here we ask the content resolver to persist the permission for us
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(
treeUri,
takeFlags
)
val treeUriAsString = treeUri.toString()
tinyDB.putString("FOLDER_URI", treeUriAsString)
if (SDK_INT >= Build.VERSION_CODES.R) {
setupPaths()
}
}
}
}
}
private fun setupPaths() {
setUpOverlay()
fetchWhatsAppRootURIs(
this,
sharedViewModel,
dataRepository,
tinyDB
) {
fetchWhatsAppBusinessRootURIs(
this,
sharedViewModel,
dataRepository,
tinyDB
) {
tinyDB.putBoolean("WARootPathsDone", true)
removeOverlay()
}
}
}
override fun onDestroy() {
dialogHandler.removeCallbacksAndMessages(null)
super.onDestroy()
}
val loadmanagerImages = object : LoaderManager.LoaderCallbacks<Cursor> {
val whatsAppImagesArrayList = arrayListOf<File>()
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
var location: File = File(
Environment.getExternalStorageDirectory()
.toString() + Constants.whatsapp_images_path
)
if (!location.exists()) {
location = File(
Environment.getExternalStorageDirectory()
.toString() + Constants.whatsapp_images_path11
)
}
if (location != null && location.exists()) {
whatsAppImagesArrayList.clear()
Timber.e("checkLoaded-onCreateLoader $id")
if (id == 0) {
var folder = location.absolutePath
val projection = arrayOf(
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DATE_MODIFIED
)
val selection = MediaStore.Images.Media.DATA + " like ? "
val selectionArgs: String = "%$folder%"
return CursorLoader(
this#HomeActivity,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
selection,
arrayOf(selectionArgs),
"${MediaStore.Images.Media.DATE_MODIFIED} DESC"
)
}
}
return null!!
}
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
Timber.e("checkLoaded-onLoadFinished")
var absolutePathOfImage: String
if (loader.id == 0) {
cursor?.let {
val columnIndexData = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
GlobalScope.launch(Dispatchers.Main + exceptionHandler) {
async(Dispatchers.IO + exceptionHandler) {
while (!cursor.isClosed && cursor.moveToNext() == true) {
absolutePathOfImage = cursor.getString(columnIndexData!!)
whatsAppImagesArrayList.add(File(absolutePathOfImage))
}
}.await()
LoaderManager.getInstance(this#HomeActivity).destroyLoader(0)
Timber.e("checkLoaded-Completion")
galleryViewModel.whatsAppImagesList.postValue(whatsAppImagesArrayList)
}
}
}
}
override fun onLoaderReset(loader: Loader<Cursor>) {
}
}
fun loadWhatsAppImages() {
try {
tinyDB.putBoolean("whatsAppMediaLoadCalled", true)
LoaderManager.getInstance(this).initLoader(
0,
null,
loadmanagerImages
)
} catch (e: RuntimeException) {
Log.e("exVideos ", "ex : ${e.localizedMessage}")
}
}
companion object {
const val ANDROID_DOCID = "primary:Android/media/"
const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
private fun openDocumentTree() {
val uriString = tinyDB.getString("FOLDER_URI", "")
when {
uriString == "" -> {
Log.w(LOGTAG, "uri not stored")
askPermission()
}
arePermissionsGranted() -> {
}
else -> {
Log.w(LOGTAG, "uri permission not stored")
askPermission()
}
}
}
// this will present the user with folder browser to select a folder for our data
private fun askPermission() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidUri)
startActivityForResult(intent, REQUEST_CODE)
}
private fun arePermissionsGranted(): Boolean {
var uriString = tinyDB.getString("FOLDER_URI", "")
val list = contentResolver.persistedUriPermissions
for (i in list.indices) {
val persistedUriString = list[i].uri.toString()
if (persistedUriString == uriString && list[i].isWritePermission && list[i].isReadPermission) {
return true
}
}
return false
}
private fun showWrongFolderSelection() {
val layoutInflaterAndroid = LayoutInflater.from(this)
val mView = layoutInflaterAndroid.inflate(R.layout.layout_dialog_wrong_folder, null)
val builder = AlertDialog.Builder(this, R.style.ThemePageSearchDialog)
builder.setView(mView)
val alertDialog = builder.show()
alertDialog.setCancelable(false)
val btnOk = mView.findViewById(R.id.tvExit) as TextView
val tvCancel = mView.findViewById(R.id.tvCancel) as TextView
btnOk.setOnClickListener {
alertDialog.dismiss()
openDocumentTree()
}
tvCancel.setOnClickListener {
alertDialog.dismiss()
}
}
private fun setUpWAURIs() {
dataRepository.mrWhatsAppImages =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppImages")
)
dataRepository.mrWhatsAppVN =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppVN")
)
dataRepository.mrWhatsAppDocs =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppDocs")
)
dataRepository.mrWhatsAppVideo =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppVideo")
)
dataRepository.mrWhatsAppAudio =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppAudio")
)
dataRepository.WhatsAppStatuses =
getDocumentFileFromStringURIStatuses(
this,
tinyDB.getString("WhatsAppStatuses")
)
dataRepository.mrWhatsAppBusinessImages =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppBusinessImages")
)
dataRepository.mrWhatsAppBusinessVN =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppBusinessVN")
)
dataRepository.mrWhatsAppBusinessDocs =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppBusinessDocs")
)
dataRepository.mrWhatsAppBusinessVideo =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppBusinessVideo")
)
dataRepository.mrWhatsAppBusinessAudio =
getDocumentFileFromStringURI(
this,
tinyDB.getString("mrWhatsAppBusinessAudio")
)
dataRepository.WhatsAppBusinessStatuses =
getDocumentFileFromStringURIStatuses(
this,
tinyDB.getString("WhatsAppBusinessStatuses")
)
}
fun setUpOverlay() {
val dialogfragment = FullScreenLoadingDialog()
dialogfragment.isCancelable = false
dialogfragment.setisAdmobAd(true)
val ft: FragmentTransaction =
supportFragmentManager.beginTransaction()
ft.add(dialogfragment, "DialogFragment_FLAG")
ft.commitAllowingStateLoss()
}
fun removeOverlay() {
val fragment: Fragment? = supportFragmentManager.findFragmentByTag("DialogFragment_FLAG")
if (fragment != null && fragment is DialogFragment) {
fragment.dismissAllowingStateLoss()
}
}
fun fetchWhatsAppRootURIs(
context: Context,
sharedViewModel: SharedViewModel,
dataRepository: DataRepository,
tinyDB: TinyDB, completed: () -> Unit
) {
val selectedPackageName = Constants.WHATSAPP_PKG_NAME
val selectedRootName = Constants.WHATSAPP_ROOT_NAME
var waImages: DocumentFile? = null
var waVN: DocumentFile? = null
var waDocs: DocumentFile? = null
var waVideos: DocumentFile? = null
var waAudio: DocumentFile? = null
var waStatus: DocumentFile? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && sharedViewModel.treeUri != null) {
CoroutineScope(Dispatchers.Main).launch {
async(Dispatchers.IO) {
val dir = DocumentFile.fromTreeUri(
context,
sharedViewModel.treeUri!!
)
dir?.listFiles()?.forEach {
if (it.name.equals(selectedPackageName)) {
it.listFiles().forEach {
if (it.name.equals(selectedRootName)) {
it.listFiles().forEach {
if (it.name.equals(Constants.WHATSAPP_MEDIA_FOLDER_NAME)) {
it.listFiles().forEach {
if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_IMAGES)) {
waImages = it
} else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_VN)) {
waVN = it
} else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_DOCUMENTS)) {
waDocs = it
} else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_VIDEO)) {
waVideos = it
} else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_AUDIO)) {
waAudio = it
} else if (it.name.equals(Constants.FOLDER_NAME_STATUSES)) {
waStatus = it
}
}
}
}
}
}
}
}
}.await()
Timber.e("processStatusFetch:Done")
tinyDB.putString("mrWhatsAppImages", waImages?.uri.toString())
tinyDB.putString("mrWhatsAppVN", waImages?.uri.toString())
tinyDB.putString("mrWhatsAppDocs", waImages?.uri.toString())
tinyDB.putString("mrWhatsAppVideo", waImages?.uri.toString())
tinyDB.putString("mrWhatsAppAudio", waImages?.uri.toString())
tinyDB.putString("WhatsAppStatuses", waStatus?.uri.toString())
dataRepository.mrWhatsAppImages = waImages
dataRepository.mrWhatsAppVN = waVN
dataRepository.mrWhatsAppDocs = waDocs
dataRepository.mrWhatsAppVideo = waVideos
dataRepository.mrWhatsAppAudio = waAudio
dataRepository.WhatsAppStatuses = waStatus
completed()
}
}
}
Here i am using .Statuses folder URI to list DocumentFiles and display but this way it is slow
class StatusImageFragment : Fragment(), StatusListener, CoroutineScope {
companion object {
fun newInstance() = StatusImageFragment()
}
val handler = CoroutineExceptionHandler { _, exception ->
Log.d("CoroutineException", "$exception handled !")
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job + handler
private lateinit var job: Job
private var adapterSDK30 = StatusImageAdapterSDK30()
private var no_image: ImageView? = null
private var no_image_txt: TextView? = null
val tinyDB: TinyDB by inject()
val sharedViewModel by viewModel<SharedViewModel>()
private val dataRepository: DataRepository by inject()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
job = Job()
return inflater.inflate(R.layout.status_image_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
swipeRefresh(false, false)
}
public fun swipeRefresh(isReloadRequired: Boolean, isFromModeChanged: Boolean) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (isFromModeChanged) {
status_image_recycler.visibility = View.GONE
progressbar.visibility = View.VISIBLE
no_image?.let {
it.visibility = View.GONE
}
no_image_txt?.let {
it.visibility = View.GONE
}
go_to_app?.let {
it.visibility = View.GONE
}
} else {
if (adapterSDK30.listImages == null || adapterSDK30.listImages.size == 0) {
no_image?.let {
it.visibility = View.GONE
}
no_image_txt?.let {
it.visibility = View.GONE
}
go_to_app?.let {
it.visibility = View.GONE
}
progressbar.visibility = View.VISIBLE
}
}
if (isReloadRequired) {
processStatusFetchFromChild({
sharedViewModel.statusImages.observe(viewLifecycleOwner, Observer {
val arrayList = it
adapterSDK30.listImages = arrayList
postFetchingExecutionSDK30()
})
})
} else {
sharedViewModel.statusImages.observe(viewLifecycleOwner, Observer {
val arrayList = it
adapterSDK30.listImages = arrayList
adapterSDK30.listImages = it
postFetchingExecutionSDK30()
})
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
private fun postFetchingExecutionSDK30() {
progressbar.visibility = View.GONE
status_image_recycler.visibility = View.VISIBLE
if (adapterSDK30!!.listImages != null && adapterSDK30!!.listImages.size > 0) {
no_image?.let {
it.visibility = View.GONE
}
no_image_txt?.let {
it.visibility = View.GONE
}
go_to_app?.let {
it.visibility = View.GONE
}
} else {
no_image?.let {
it.visibility = View.VISIBLE
}
no_image_txt?.let {
it.visibility = View.VISIBLE
}
go_to_app?.let {
it.visibility = View.VISIBLE
}
}
adapterSDK30!!.notifyDataSetChanged()
status_img_swipe.isRefreshing = false
}
override fun onDestroyView() {
job.cancel()
super.onDestroyView()
}
fun processStatusFetchFromChild(completed: () -> Unit) {
val statusSelection = tinyDB.getInt(Constants.status_accounts)
if (statusSelection == 0 || statusSelection == 1) {
if (dataRepository.WhatsAppStatuses == null) {
(activity as StatusActivity).setUpWAURIs()
}
var documentFileStatuses: DocumentFile? = dataRepository.WhatsAppStatuses
if (statusSelection == 1) {
documentFileStatuses = dataRepository.WhatsAppBusinessStatuses
}
if (documentFileStatuses != null) {
launch(Dispatchers.Main) {
val statusImages1 = arrayListOf<DocumentFile>()
async(Dispatchers.IO) {
//this takes time ; want to fetch this same as WhatsApp Gallery
statusImages1.addAll(documentFileStatuses!!.listFiles().filter {
it.mimeType.equals(Constants.MIME_TYPE_IMG_PNG) || it.mimeType.equals(
Constants.MIME_TYPE_IMG_JPG
) || it.mimeType.equals(Constants.MIME_TYPE_IMG_JPEG)
}.sortedByDescending { it.lastModified() })
}.await()
Timber.e("processStatusFetch:Done")
sharedViewModel.statusImages.postValue(statusImages1)
completed()
}
} else {
Timber.e("processStatusFetch:Done")
sharedViewModel.statusImages.postValue(arrayListOf<DocumentFile>())
completed()
}
} else {
Timber.e("processStatusFetch:Done")
sharedViewModel.statusImages.postValue(arrayListOf<DocumentFile>())
completed()
}
}
}
Please note WhatsApp folder path which i used is
val whatsapp_images_path11 = "/Android/media/“ +"com.whatsapp" +"/WhatsApp/Media/WhatsAppImages/"
How i can use MediaStore in this case so that i don't need to use sort and filter functions of list? Its not important to get java.io File only i can work with URIs as well.
What I have finally implemented, in android 10+ you need to ask the user for your specific directory access. Then you can use this functions to fetch statuses:
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun readSDKFrom30(): ArrayList<String> {
val treeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
"primary:Android/media/com.whatsapp/WhatsApp/Media/.Statuses"
)
val tree = DocumentFile.fromTreeUri(context, treeUri)!!
val pathList = ArrayList<String>()
listFolderContent(tree).forEach { uri ->
val file = createFileFromContentUri(uri)
pathList.add(file.toString())
}
return pathList
}
private fun listFolderContent(folder: DocumentFile): List<Uri> {
return if (folder.isDirectory) {
val files = folder.listFiles().toMutableList()
files.sortByDescending { it.lastModified() }
files.mapNotNull { file ->
if (file.name != null) file.uri else null
}
} else {
emptyList()
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createFileFromContentUri(fileUri: Uri): File {
var fileName = ""
fileUri.let { returnUri ->
context.contentResolver.query(returnUri, null, null, null)
}?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
}
val iStream: InputStream =
context.contentResolver.openInputStream(fileUri)!!
val outputDir: File = context.cacheDir!!
val outputFile = File(outputDir, fileName)
copyStreamToFile(iStream, outputFile)
iStream.close()
return outputFile
}
private fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
inputStream.use { input ->
val outputStream = FileOutputStream(outputFile)
outputStream.use { output ->
val buffer = ByteArray(4 * 1024) // buffer size
while (true) {
val byteCount = input.read(buffer)
if (byteCount < 0) break
output.write(buffer, 0, byteCount)
}
output.flush()
}
}
}
Using DocumentFile to handle SAF uries is slow indeed.
Better use DocumentsContract to do so.
Its about twenty times as fast as DocumentFile and about as fast as classic File class stuff.
Using MediaStore for hidden folders should be possible. You cannot create hidden folders with the mediastore. But if you managed to make them not using mediastore you should be able to list files in them using mediastore. Well if they are scanned. And if they belong to your app.

Application not responding after implementing Scope Storage

I implemented Scope Storage in a sample project. Where I Save, Load, and Modify Images in Local Storage and in Scope Storage as well.
Below is my Main Activity Class where I am Saving, Loading, and Modifying images in Local and Scope Storage. The below Code was working in API level 28 or Android 10. but when I run this app in Android 11 it gets hang and gives Application Not Responding. I am unable to find any error in Logcat. Below is my code Manifest File:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.scopestorage">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.ScopeStorage">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Now Below is my Main Activity Code:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var internalStoragePhotoAdapter: InternalStoragePhotoAdapter
private lateinit var externalStoragePhotoAdapter: SharedPhotoAdapter
private var readPermissionGranted = false
private var writePermissionGranted = false
private lateinit var permissionsLauncher: ActivityResultLauncher<Array<String>>
private lateinit var intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>
private lateinit var contentObserver: ContentObserver
private var deletedImageUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
internalStoragePhotoAdapter = InternalStoragePhotoAdapter {
lifecycleScope.launch {
val isDeletionSuccessful = deletePhotoFromInternalStorage(it.name)
if(isDeletionSuccessful) {
loadPhotosFromInternalStorageIntoRecyclerView()
Toast.makeText(this#MainActivity, "Photo successfully deleted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this#MainActivity, "Failed to delete photo", Toast.LENGTH_SHORT).show()
}
}
}
externalStoragePhotoAdapter = SharedPhotoAdapter {
lifecycleScope.launch {
deletePhotoFromExternalStorage(it.contentUri)
deletedImageUri = it.contentUri
}
}
setupExternalStorageRecyclerView()
initContentObserver()
permissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
readPermissionGranted = permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: readPermissionGranted
writePermissionGranted = permissions[Manifest.permission.WRITE_EXTERNAL_STORAGE] ?: writePermissionGranted
if(readPermissionGranted) {
loadPhotosFromExternalStorageIntoRecyclerView()
} else {
Toast.makeText(this, "Can't read files without permission.", Toast.LENGTH_LONG).show()
}
}
updateOrRequestPermissions()
intentSenderLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if(it.resultCode == RESULT_OK) {
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
lifecycleScope.launch {
deletePhotoFromExternalStorage(deletedImageUri ?: return#launch)
}
}
Toast.makeText(this#MainActivity, "Photo deleted successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this#MainActivity, "Photo couldn't be deleted", Toast.LENGTH_SHORT).show()
}
}
val takePhoto = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) {
lifecycleScope.launch {
val isPrivate = binding.switchPrivate.isChecked
val isSavedSuccessfully = when {
isPrivate -> savePhotoToInternalStorage(UUID.randomUUID().toString(), it)
writePermissionGranted -> savePhotoToExternalStorage(UUID.randomUUID().toString(), it)
else -> false
}
if(isPrivate) {
loadPhotosFromInternalStorageIntoRecyclerView()
}
if(isSavedSuccessfully) {
Toast.makeText(this#MainActivity, "Photo saved successfully", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this#MainActivity, "Failed to save photo", Toast.LENGTH_SHORT).show()
}
}
}
binding.btnTakePhoto.setOnClickListener {
takePhoto.launch()
}
setupInternalStorageRecyclerView()
loadPhotosFromInternalStorageIntoRecyclerView()
loadPhotosFromExternalStorageIntoRecyclerView()
}
private fun initContentObserver() {
contentObserver = object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
if(readPermissionGranted) {
loadPhotosFromExternalStorageIntoRecyclerView()
}
}
}
contentResolver.registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
true,
contentObserver
)
}
private suspend fun deletePhotoFromExternalStorage(photoUri: Uri) {
withContext(Dispatchers.IO) {
try {
contentResolver.delete(photoUri, null, null)
} catch (e: SecurityException) {
val intentSender = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
MediaStore.createDeleteRequest(contentResolver, listOf(photoUri)).intentSender
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
val recoverableSecurityException = e as? RecoverableSecurityException
recoverableSecurityException?.userAction?.actionIntent?.intentSender
}
else -> null
}
intentSender?.let { sender ->
intentSenderLauncher.launch(
IntentSenderRequest.Builder(sender).build()
)
}
}
}
}
private suspend fun loadPhotosFromExternalStorage(): List<SharedStoragePhoto> {
return withContext(Dispatchers.IO) {
val collection = sdk29AndUp {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} ?: MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT,
)
val photos = mutableListOf<SharedStoragePhoto>()
contentResolver.query(
collection,
projection,
null,
null,
"${MediaStore.Images.Media.DISPLAY_NAME} ASC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val displayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH)
val heightColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT)
while(cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val displayName = cursor.getString(displayNameColumn)
val width = cursor.getInt(widthColumn)
val height = cursor.getInt(heightColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
photos.add(SharedStoragePhoto(id, displayName, width, height, contentUri))
}
photos.toList()
} ?: listOf()
}
}
private fun updateOrRequestPermissions() {
val hasReadPermission = ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
val hasWritePermission = ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
val minSdk29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
readPermissionGranted = hasReadPermission
writePermissionGranted = hasWritePermission || minSdk29
val permissionsToRequest = mutableListOf<String>()
if(!writePermissionGranted) {
permissionsToRequest.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if(!readPermissionGranted) {
permissionsToRequest.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if(permissionsToRequest.isNotEmpty()) {
permissionsLauncher.launch(permissionsToRequest.toTypedArray())
}
}
private suspend fun savePhotoToExternalStorage(displayName: String, bmp: Bitmap): Boolean {
return withContext(Dispatchers.IO) {
val imageCollection = sdk29AndUp {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} ?: MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$displayName.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.WIDTH, bmp.width)
put(MediaStore.Images.Media.HEIGHT, bmp.height)
}
try {
contentResolver.insert(imageCollection, contentValues)?.also { uri ->
contentResolver.openOutputStream(uri).use { outputStream ->
if(!bmp.compress(Bitmap.CompressFormat.JPEG, 95, outputStream)) {
throw IOException("Couldn't save bitmap")
}
}
} ?: throw IOException("Couldn't create MediaStore entry")
true
} catch(e: IOException) {
e.printStackTrace()
false
}
}
}
private fun setupInternalStorageRecyclerView() = binding.rvPrivatePhotos.apply {
adapter = internalStoragePhotoAdapter
layoutManager = StaggeredGridLayoutManager(3, RecyclerView.VERTICAL)
}
private fun setupExternalStorageRecyclerView() = binding.rvPublicPhotos.apply {
adapter = externalStoragePhotoAdapter
layoutManager = StaggeredGridLayoutManager(3, RecyclerView.VERTICAL)
}
private fun loadPhotosFromInternalStorageIntoRecyclerView() {
lifecycleScope.launch {
val photos = loadPhotosFromInternalStorage()
internalStoragePhotoAdapter.submitList(photos)
}
}
private fun loadPhotosFromExternalStorageIntoRecyclerView() {
lifecycleScope.launch {
val photos = loadPhotosFromExternalStorage()
externalStoragePhotoAdapter.submitList(photos)
}
}
private suspend fun deletePhotoFromInternalStorage(filename: String): Boolean {
return withContext(Dispatchers.IO) {
try {
deleteFile(filename)
} catch (e: Exception) {
e.printStackTrace()
false
}
}
}
private suspend fun loadPhotosFromInternalStorage(): List<InternalStoragePhoto> {
return withContext(Dispatchers.IO) {
val files = filesDir.listFiles()
files?.filter { it.canRead() && it.isFile && it.name.endsWith(".jpg") }?.map {
val bytes = it.readBytes()
val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
InternalStoragePhoto(it.name, bmp)
} ?: listOf()
}
}
private suspend fun savePhotoToInternalStorage(filename: String, bmp: Bitmap): Boolean {
return withContext(Dispatchers.IO) {
try {
openFileOutput("$filename.jpg", MODE_PRIVATE).use { stream ->
if(!bmp.compress(Bitmap.CompressFormat.JPEG, 95, stream)) {
throw IOException("Couldn't save bitmap.")
}
}
true
} catch(e: IOException) {
e.printStackTrace()
false
}
}
}
override fun onDestroy() {
super.onDestroy()
contentResolver.unregisterContentObserver(contentObserver)
}
}
Any sort of help would be highly appreciated.

how to increase listening time of speech recognition (Speech to Text) android (SpeechRecognizer)

I am working on app which require speech to text. I have integrated SpeechRecognizer service. Please check below demo project code for same. Here I have tested that SpeechRecognizer stop listening automatically after 10 to 15 seconds. So I looked for following solution to increase the listening time but did not worked.
Solution 1: SpeechRecognizer - time limit
For the above link i found the problem is text getting cut like Said "Hello , what is the serial number of your currency, It is ABC5654548585" so in first result i got Hello, What is the serial number of your currency and in second i got it is ABC5654548585".So I am unable to parse response properly.
Solution 2: I set the following parameters but did not worked.
EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS
EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS
EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS.
Solution 3: I also integrated mozilla speech to text but it is not accurate as google.
Please help me I am stucked here.Not able to proceed any more.
class MainActivity : AppCompatActivity() {
lateinit var speechRecognize: SpeechRecognizer
var editText: EditText? = null
var micButton: ImageView? = null
var speechRecognizerIntent: Intent? = null
var audioManager: AudioManager? = null
var textRecorded = ""
var isActive = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
speechRecognize = SpeechRecognizer.createSpeechRecognizer(this)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager?
muteRecognition(true)
editText = findViewById(R.id.text);
micButton = findViewById(R.id.button);
micButton?.setOnClickListener {
if (it.tag == null) {
isActive = true
launchSpeechIntent()
speechRecognize.startListening(speechRecognizerIntent)
it.tag = 1
} else if (it.tag == 1) {
isActive = false
parseText()
speechRecognize.stopListening()
speechRecognize.destroy()
it.tag = null
}
}
val permissions = ArrayList<String>()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
)
!= PackageManager.PERMISSION_GRANTED
) {
permissions.add(Manifest.permission.RECORD_AUDIO)
}
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissions.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissions.toArray(arrayOf<String>()),
10
)
} else
launchSpeechIntent()
//else
// startRequest()
}
#Suppress("DEPRECATION")
private fun muteRecognition(mute: Boolean) {
audioManager?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val flag = if (mute) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE
it.adjustStreamVolume(AudioManager.STREAM_NOTIFICATION, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_ALARM, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_MUSIC, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_RING, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_SYSTEM, flag, 0)
} else {
it.setStreamMute(AudioManager.STREAM_NOTIFICATION, mute)
it.setStreamMute(AudioManager.STREAM_ALARM, mute)
it.setStreamMute(AudioManager.STREAM_MUSIC, mute)
it.setStreamMute(AudioManager.STREAM_RING, mute)
it.setStreamMute(AudioManager.STREAM_SYSTEM, mute)
}
}
}
fun launchSpeechIntent() {
speechRecognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
speechRecognizerIntent?.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
);
speechRecognizerIntent?.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.packageName)
speechRecognizerIntent?.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
speechRecognizerIntent?.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1)
speechRecognizerIntent?.putExtra(
RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS,
30000
)
speechRecognize.setRecognitionListener(object : RecognitionListener {
override fun onReadyForSpeech(params: Bundle?) {
Log.e("ready for speeach", "true")
}
override fun onRmsChanged(rmsdB: Float) {
Log.e("RMS changed", rmsdB.toString())
}
override fun onBufferReceived(buffer: ByteArray?) {
Log.e("buffer", buffer.toString())
}
override fun onPartialResults(partialResults: Bundle?) {
Log.e("ready for speeach", "true" + partialResults.toString())
}
override fun onEvent(eventType: Int, params: Bundle?) {
Log.e("event", eventType.toString())
Log.e("params", params.toString())
}
override fun onBeginningOfSpeech() {
editText!!.setHint("Listening...........")
editText!!.setText("")
}
override fun onEndOfSpeech() {
}
override fun onError(error: Int) {
Log.e("Error", error.toString())
speechRecognize.startListening(speechRecognizerIntent)
//editText?.setText("Error:"+error.toString())
}
override fun onResults(results: Bundle?) {
val data: ArrayList<String>? =
results!!.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
var flot = results!!.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES)
textRecorded += "#${data!!.first()}"
// restart()
}
})
}
fun restart() {
if (isActive) {
speechRecognize.destroy()
launchSpeechIntent()
speechRecognize.startListening(speechRecognizerIntent)
} else {
speechRecognize.stopListening()
speechRecognize.destroy()
}
}
override fun onResume() {
super.onResume()
}
fun parseText() {
try {
editText?.setText(textRecorded)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
10
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
10 ->
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
launchSpeechIntent()
}
}
}
override fun onDestroy() {
super.onDestroy()
speechRecognize.stopListening()
speechRecognize.destroy()
}
}

Categories

Resources