Symptoms: upon first start my app crashes with java.lang.SecurityException: Lacking privileges to access camera service. I am getting "Unfortunately your app has crashed" dialog, clicking "OK", under this dialog there are two dialogs asking for the necessary permissions. I say "OK" to both and from now on my app works. Next starts are without the crash.
Causes: After some time of reading and debugging I came to understanding that my app problem is that it wants to do some Camera related logic before it obtains the required permissions (onSurfaceTextureAvailable callback in my CameraHandler class) or before camera surface view comes to foreground.
There's a lot of questions regarding similar errors on SO and Github, but still it's hard for me to figure out.
I tried to go through this answer, but my setup is a bit different, i.e. I have my Camera logic inside a different class that isn't an Activity, and I would really like to keep it that way not to clutter my CameraActivity class. Is there a good way to deal with this?
How to make sure that when onSurfaceTextureAvailable in my CameraHandler class is fired, the permissions are already granted, so that I am not getting java.lang.SecurityException: Lacking privileges to access camera service on the first run?
This is my SurfaceTextureListener located in CameraHandler class:
private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
openCamera(width, height) //this line here makes my app crash
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
configureTransform(width, height)
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
}
}
My CameraActivity onCreate, onResume() and onPause():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!canAccessCamera() || !canRecordAudio()) {
requestPermissions(INITIAL_PERMISSIONS, INITIAL_REQUEST)
}
}
}
override fun onResume() {
super.onResume()
cameraHandler.startHandler()
}
override fun onPause() {
cameraHandler.stopHandler()
super.onPause()
}
permission checking inside CameraActivity
#RequiresApi(api = Build.VERSION_CODES.M)
private fun canAccessCamera() : Boolean {
return (hasPermission(android.Manifest.permission.CAMERA))
}
#RequiresApi(api = Build.VERSION_CODES.M)
private fun canRecordAudio() : Boolean {
return (hasPermission(android.Manifest.permission.RECORD_AUDIO))
}
#RequiresApi(api = Build.VERSION_CODES.M)
private fun hasPermission(perm : String) : Boolean{
return (PackageManager.PERMISSION_GRANTED == checkSelfPermission(perm))
}
#RequiresApi(Build.VERSION_CODES.M)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == INITIAL_REQUEST) {
if (canAccessCamera() && canRecordAudio()) {
recordButton2.setOnClickListener {
if (isRecording) {
cameraHandler.endRecording()
} else {
currentFileName = generateTimestampName()
createCSVFile(currentFileName)
cameraHandler.startStopRecording()
}
isRecording = !isRecording
}
} else {
Toast.makeText(this, "We need it to perform magic", Toast.LENGTH_SHORT).show()
}
}
}
I've written myself a little PermissionRequestHandler for exactly this purpose, you can find it here: https://gist.github.com/Hikaru755/0ae45a4184bdbc28dcc5c1af659b4508
You simply create an instance of it in your Activity, and make sure that any calls to onRequestPermissionsResult are passed to it, like in the example BaseActivity in the gist. Then you can pass it around to other classes, request permission through it and get callbacks where you need them without cluttering your Activity. Be careful not to create memory leaks by holding it longer than the Activity lives, though!
Related
I have a simple empty activity that checks if permissions need to be requested. When registerForActivityResult is called, it crashes with the error java.lang.IllegalStateException: LifecycleOwner com.example.app.PermsRequester#41a30da is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED. From my research, I think I need to check if savedInstanceState is null and if so, create a new fragment? I'm not sure if that is the correct solution or how to implement. Below is the code:
class PermsRequester : AppCompatActivity() {
requestPerms = false
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_perms_requester)
findViewById<Button>(R.id.acceptButton).setOnClickListener { accepted() }
}
private fun accepted() {
//There is code here to check if rationale dialog needs to be displayed
//There is code here to build a mutable list of permissions that need to be requested and sets requestPerms = true
if(requestPerms)
requestPermissions()
}
private fun requestPermissions() {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//app crashes on the following line
val requestMultiplePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
permissions -> permissions.entries.forEach {
//Handles permission result
}
}
}
}
}
You need to registerForActivityResult before onStart of the Activity.
private lateinit var requestMultiplePermissionsLauncher:
ActivityResultLauncher<Array<String>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestMultiplePermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach {
//Handles permission result
}
}
}
private fun accepted() {
if(requestPerms) {
val permissions = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.CAMERA)
requestMultiplePermissionsLauncher.launch(permissions)
}
}
I'm trying to make the basic ZXing functionality to scan qr codes using their basic instructions, but my camera does not open it just goes to the blank ScanActivity.
I've already added the "implementation" on module app dependency
implementation 'me.dm7.barcodescanner:zxing:1.9'
and permission on AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA"/>
I've also manually allowed its permission on the settings of the android phone i'm testing it on
My main activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvResult = tvresult
btn.setOnClickListener {
val intent = Intent(this#MainActivity, ScanActivity::class.java)
startActivity(intent)
}
}
companion object {
var tvResult: TextView? = null
}
}
ScanActivity Class
class ScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
private var mScannerView: ZXingScannerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this)
setContentView(R.layout.activity_scan)
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
}
public override fun onResume() {
super.onResume()
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
mScannerView!!.startCamera() // Start camera on resume
}
public override fun onPause() {
super.onPause()
mScannerView!!.stopCamera() // Stop camera on pause
}
override fun handleResult(rawResult: Result) {
// Do something with the result here
// Log.v("tag", rawResult.getText()); // Prints scan results
// Log.v("tag", rawResult.getBarcodeFormat().toString()); // Prints the scan format (qrcode, pdf417 etc.)
MainActivity.tvResult!!.setText(rawResult.text)
onBackPressed()
// If you would like to resume scanning, call this method below:
//mScannerView.resumeCameraPreview(this);
}}
after i click on the Scan barcode button it just goes to this
(im expecting the camera will open because of the startCamera()
I had a similar issue, it was permission related.
Try adding the following within the onCreate(..)
if (ContextCompat.checkSelfPermission(this#MainActivity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this#MainActivity, arrayOf(Manifest.permission.CAMERA), 123)
}
Late answer, by the way if someone else has this problem, you forgot to add mScannerView in your Layout
I have copied/created the code below as per the camerakit docs. but when I press the button, nothing happens!
I am using v1.0.0 beta 3.11 version. The docs section only provides basic info and not detailed example with settings, unless I am reading the wrong page.
package com.example.eg
import android.support.v7.app.AppCompatActivity
import com.example.eg.R
import android.os.Bundle
import android.util.Log
import com.camerakit.CameraKitView
import java.io.FileOutputStream
import android.widget.Toast
import com.camerakit.CameraKit
import kotlinx.android.synthetic.main.activity_camera.*
class Camera_Activity : AppCompatActivity() {
private lateinit var cameraKitView: CameraKitView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
cameraKitView = findViewById<CameraKitView>(R.id.camera)
cameraKitView.requestPermissions(this) // I have permission="camera" in xml
val filePath = intent.getStringExtra("path")
cameraSettings()
cameraKitView.errorListener = CameraKitView.ErrorListener { cameraKitView, e ->
Toast.makeText(this,"Camera error!",Toast.LENGTH_SHORT).show()
}
button.setOnClickListener {
cameraKitView.captureImage(object:CameraKitView.ImageCallback {
override fun onImage(p0: CameraKitView?, p1: ByteArray?) {
// Code never reaches here
if (LOG_ENABLED) Log.e("Picture taken?: ","YES--------")
val outputStream = FileOutputStream(filePath)
outputStream.write(p1)
outputStream.close()
finish()
}
})
}
}
override fun onStart() {
super.onStart()
cameraKitView.onStart()
}
override fun onResume() {
super.onResume()
cameraKitView.onResume()
}
override fun onPause() {
cameraKitView.onPause()
super.onPause()
}
override fun onStop() {
cameraKitView.onStop()
super.onStop()
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
cameraKitView.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun cameraSettings(){
//OPTIONS BELOW: back, FACING_BACK front, FACING_FRONT
cameraKitView.setFacing(CameraKit.FACING_BACK)
//cameraKitView.toggleFacing()
cameraKitView.imageMegaPixels = 1f //1 megapixel
//OPTIONS BELOW: off, FLASH_OFF on, FLASH_ON
//cameraKitView.setFlash(CameraKit.FLASH_OFF)
//OPTIONS BELOW: auto, FOCUS_AUTO continuous, FOCUS_CONTINUOUS off, FOCUS_OFF
cameraKitView.setFocus(CameraKit.FOCUS_AUTO)
//OPTIONS BELOW: camera all audio location storage none
//cameraKitView.setPermissions()
}
}
My manifest file has camera and write external storage permissions and I know the external storage permission works because of other activities being able to do it without problem.
While I am at it I would like to request a source of information where this camerakit api is explained in detail so not only experts can understand it but junior level people can also have a go at it.
It's an issue caused by the Kotlin coroutines with the libraries. I read that they are working on it, i solved it using the 3.10 instead of 3.11 version.
I want to disable the WiFi when the app is closed.
i know the code to disable WiFi using this line :
wifiManager!!.isWifiEnabled = false
but i don't know how to detect the closing of the app.
This exactly what lifecycles are used for. Any clean up work that needs to done should be done in onDestroy(). This is the final call you receive before your activity is destroyed. So in the activity where you want to disable wifi you can just do:
override func onDestroy() {
super.onDestroy();
wifiManager!!.isWifiEnabled = false;
}
You might check out this blog post. It described how to do it more detail than I could.
EDIT:
Important parts of blog post are:
1 - Create our interface that will be implemented by a custom Application class:
interface LifecycleDelegate {
fun onAppBackgrounded()
fun onAppForegrounded()
}
2 - Now we a class that is going to implement the ActivityLifecycleCallbacks and ComponentCallbacks2:
class AppLifecycleHandler(
private val lifeCycleDelegate: LifeCycleDelegate
) : Application.ActivityLifecycleCallbacks, ComponentCallbacks2
{
private var appInForeground = false
override fun onActivityResumed(activity: Activity?) {
if (!appInForeground) {
appInForeground = true
lifeCycleDelegate.onAppForegrounded()
}
}
override fun onTrimMemory(level: Int) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
appInForeground = false
lifeCycleDelegate.onAppBackgrounded()
}
}
// stub other methods
}
3 - We need to use that handler in our application class:
class App : Application(), LifeCycleDelegate {
override fun onCreate() {
super.onCreate()
val lifeCycleHandler = AppLifecycleHandler(this)
registerLifecycleHandler(lifeCycleHandler)
}
override fun onAppBackgrounded() {
Log.d("App", "App in background")
}
override fun onAppForegrounded() {
Log.d("App", "App in foreground")
}
private fun registerLifecycleHandler(lifeCycleHandler: AppLifecycleHandler) {
registerActivityLifecycleCallbacks(lifeCycleHandler)
registerComponentCallbacks(lifeCycleHandler)
}
}
In a fragment, I have a downloading code. and I'm sure I need the download function in the other fragments too.
So I want to make it separate file as a library from the fragment, but the code contains some android callback methods which stacked on the Activity and I don't know how to handle it if I move it to another file (Class).
The download code in the fragment,
private fun beforeDownload() {
// check permission
val externalPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (externalPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_ID_STORAGE_PERMISSION)
} else {
onDownload()
}
}
/** Android call-back method after requesting permission **/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
beforeDownload()
}
private fun onDownload() {
if (media >= 100000000) {
Toast.makeText(activity, "The media is over 100Mb", Toast.LENGTH_SHORT).show()
} else {
downloadMediaJob = launch(UI) { downloadMedia() }
}
}
// Android receiver when download completed
private val onDownloadComplete = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Toast.makeText(activity, R.string.download_complete_msg, Toast.LENGTH_SHORT).show()
}
}
suspend private fun downloadMedia() {
downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
...
downloadedFileId = downloadManager.enqueue(request)
}
and the callback methods are
onRequestPermissionsResult
onDownloadComplete
How can I move them to MediaDownload class so that making it reusable?
Each Fragment needs to implement it's own lifecycle callback, but that call back can simply delegate to a method on an instance of an object.
For example in your code above:
private fun beforeDownload() {
// check permission
val externalPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (externalPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_ID_STORAGE_PERMISSION)
} else {
onDownload()
}
}
/** Android call-back method after requesting permission **/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
beforeDownload()
}
You should just move the beforeDownload() method to some "model" class, create or inject an instance and then call the beforeDownload() method on that instance.
class SomeModel() {
fun beforeDownload() {
...
}
}
Each Fragment still needs the lifecycle method, but the main part of the code can be shared in the SomeModel class
override fun onRequestPermissionsResult(requestCode: Int, permissions:
Array<out String>, grantResults: IntArray) {
instanceOfSomeModel.beforeDownload()
}
The only way to complete remove the redundancy of having to implement even the minimal lifecycle method, would be to subclass the Fragment and add the call you your method in the override in the subclass, but you don't want to do that!