I'm new to android development, I'm building an application that supports playing video in the background. I decided to do it through Exo Player and PlayerNotificationManager. On Android 12 and below, everything works fine, but on Android 13, the notification does not want to be displayed at all, any ideas?
I use MVVM architecture, in my viewmodel's init method I initialize exoPlayer and playerNotificationManager
initialization code:
playerNotificationManager = PlayerNotificationManager.Builder(
getApplication<Application>().applicationContext,
1,
EXO_PLAYER_CHANNEL_ID)
.setSmallIconResourceId(R.drawable.racoon)
.setMediaDescriptionAdapter(mediaDescriptionAdapter)
.setFastForwardActionIconResourceId(R.drawable.ic_forward_10sec)
.setRewindActionIconResourceId(R.drawable.ic_replay_10sec)
.build()
After that, in my fragment, I assign exoPlayer to my playerNotificationManager in the onResume and onStop methods:
override fun onResume() {
super.onResume()
Log.d("MyLog", "onResume")
if (videoViewModel.playerNotificationManager != null){
videoViewModel.playerNotificationManager?.setPlayer(null)
}
}
override fun onStop() {
super.onStop()
Log.d("MyLog", "onStop")
videoViewModel.playerNotificationManager?.setPlayer(videoViewModel.exoPlayer)
}
Also I tried to register the following permissions in my manifest:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
But the result remained the same, android 13 does not want to display my notification at all
Based on #primo comment and on this links:
How do I request push notification permissions for android 13?
https://developer.android.com/training/permissions/requesting#request-permission
https://developer.android.com/develop/ui/views/notifications/notification-permission#best-practices
I did the following to solve my problem:
In onCreate method of my fragment:
if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
val launcher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean? ->
notificationGranted = isGranted == true
}
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
In onStop method:
if(VERSION.SDK_INT >= VERSION_CODES.TIRAMISU && !notificationGranted){
videoViewModel.exoPlayer.pause()
}
videoViewModel.playerNotificationManager?.setPlayer(videoViewModel.exoPlayer)
And then everything works fine.
Related
I usually add a post_notification permission request to my app.
The strange thing is that when I request directly, the dialog will not pop up.
But if I create the Notification channel first, I can jump out of the dialog normally,
Has anyone encountered the same situation? Or is there something wrong with my settings? Thanks!
AndroidManifest.xml
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Fragement:
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
super.onViewCreated(view, savedInstanceState)
requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
LogUtil.d(TAG, "Grant POST_NOTIFICATION permission")
} else {
LogUtil.d(TAG, "Denied POST_NOTIFICATION permission")
}
}
}
#RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onResume()
{
if (ContextCompat.checkSelfPermission(
activity!!.applicationContext,
Manifest.permission.POST_NOTIFICATIONS,
) == PackageManager.PERMISSION_DENIED
) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
Situation 1
if I add createNotificationChannel() before request permisssion,it work.
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
"Important Notification Channel",
NotificationManager.IMPORTANCE_HIGH,
).apply {
description = "This notification contains important announcement, etc."
}
notificationManager.createNotificationChannel(channel)
}
Situation 2
When I mark createNotificationChannel(),the dialog would not showing anymore.
After adding the Log, I found that,
After executing requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS),
there is no log jumping out,
just get the return of registerForActivityResult = false,
but I opened the app for the first time, and I have not rejected the notifcaiton permission.
I want to know how to fix it?
Step # 1
Can you change this
== PackageManager.PERMISSION_DENIED
to
!= PackageManager.PERMISSION_GRANTED
Step # 2
Ask for shouldShowRequestPermissionRationale, if user denies permission for once, on this official documentation
I am trying to open FlutterActivity in my existing android application. Before I was creating new flutter engine every time I was opening activity like this:
FlutterActivity
.withNewEngine()
.build(context)
And everything was working fine besides a little lag while opening the activity. To get rid of the lag I wanted to switch to using cached engine. I followed this official tutorial: LINK
And ended up with smething like this:
In my Application class:
class App : Application() {
lateinit var flutterEngine: FlutterEngine
override fun onCreate() {
...
flutterEngine = FlutterEngine(this)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache
.getInstance()
.put("myEngineId", flutterEngine)
}
}
And later in my application on the button click, in the same place that I was successfully opening FlutterActivity:
FlutterActivity
.withCachedEngine("myEngineId")
.build(context)
So I basically followed the all the instructions but the effect that I get now is after the button click there is even longer lag than before and then there is only black screen being displayed. My flutter screen is not displayed and application is kind of frozen I can't go back or do anything. There is also no error or any useful info in the logs. I have no idea what is going on. What am I doing wrong?
To Use cached FlutterEngine
In FlutterActivity you must declare provideFlutterEngine method.
class DemoActivity : FlutterActivity() {
override fun provideFlutterEngine(context: Context): FlutterEngine? =
FlutterEngineCache.getInstance().get(FlutterConstants.ENGINE_ID)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "demo-channel")
.setMethodCallHandler { call, result ->
if (call.method == "demo-method") {
demoMethod()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun demoMethod() {
// Do native code
}
}
I have exoplayer integrated within my application based on this link.
I have added a pending intent inside createCurrentContentIntent().
return PendingIntent.getActivity(
context, 0,
Intent(context, MyActivity::class.java), 0
)
I face an issue over here. I started playing the audio and the player notification also comes up in the status bar. My requirement is to play audio even if the app is in the background. So, I haven't released the player in onStop(). I have added the below code in onDestroy().
override fun onDestroy() {
playerNotificationManager?.setPlayer(null)
player?.stop()
player?.release()
player = null
super.onDestroy()
}
If I manually kill the application from the background when the player is playing, the notification doesn't go off. So, if I click on the notification it will crash with NullPointerException because MyActivtity is no more.
Could someone suggest a solution for the same?
I've implemented ExoPlayer along with MediaSessionCompat and MediaSessionConnector, which allows Exo to manage the media notification (and stuff like audio focus) implicitly.
class MyServiceClass {
private lateinit var player: SimpleExoPlayer
private lateinit var playerNotificationManager: PlayerNotificationManager
private lateinit var mediaSession: MediaSessionCompat
private lateinit var mediaSessionConnector: MediaSessionConnector
override fun onCreate() {
super.onCreate()
player = ...
playerNotificationManager = ...
mediaSession = MediaSessionCompat(this, CONSTANT).apply{ ..setup callback... }
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlayer(player)
playerNotificationManager.setMediaSessionToken(mediaSession.token)
playerNotificationManager.setPlayer(player)
}
override fun onDestroy() {
mediaSession.isActive = false
mediaSession.release()
mediaSessionConnector.setPlayer(null)
playerNotificationManager.setPlayer(null)
player.release()
super.onDestroy()
}
}
This should handle removing the notification, when you kill the app.
I also use a PlayerNotificationReceiver to react to notification changes by the system which is omitted in the code above. Also the whole part of triggering and reacting to notifications in the app is omitted.
I changed the complete implementation and used services. This solved the issue.
I have implemented QR Scanner inside my app. I used Firebase MLKit as well as tried Zxing Scanner.
I start new activity where qr scanner runs. Once it detects valid qr code it finishes the scanner activity and send the result back to previous activity.
Everything works without any errors.
But there is small bug probably with Camera in android.
After getting valid qr code camera shuts off for a second then starts again and after a second or two shuts down and then activity gets finishes and return result to previous activity.
I have tried Camera, Camera2, Zxing Core, MLKit with all possible combinations. Everytime same thing occurs.
I also tried couple of Github Repos
MLKitQRScanner, KotlinFirebaseMLKitQRRealtimeDemo, QRCodeCameraX, QRScannerCameraX
This is using CameraX but I also tried the older camera apis in android with same result
I think the problem is with camera preview but I tried camera2 and cameraX api. both gives same output. So it is probably something in code.
ACTIVITY
var analyzerHandler: Handler? = null
var analysis: ImageAnalysis? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scanner_x)
context = this
Dexter.withActivity(this)
.withPermissions(Manifest.permission.CAMERA)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
textureView.post {
val metrics = DisplayMetrics().also { textureView.display.getRealMetrics(it) }
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val previewConfig = PreviewConfig.Builder().apply {
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(textureView.display.rotation)
setLensFacing(CameraX.LensFacing.BACK)
}.build()
val analysisConfig = ImageAnalysisConfig.Builder().apply {
setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
val analyzerThread = HandlerThread("BarcodeFirebaseAnalyzer").apply { start() }
analyzerHandler = Handler(analyzerThread.looper)
setCallbackHandler(analyzerHandler!!)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(textureView.display.rotation)
setLensFacing(CameraX.LensFacing.BACK)
}.build()
val preview = AutoFitPreviewBuilder.build(previewConfig, textureView)
analysis = ImageAnalysis(analysisConfig)
val googlePlayServicesAvailable = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
if (googlePlayServicesAvailable == ConnectionResult.SUCCESS) {
analysis!!.analyzer = BarcodeFirebaseAnalyzer { barcode ->
toAddAuth(barcode) //THIS RUNS ONCE VALID QR CODE IS THERE. ITS JUST INTENT TO SEND BACK THE RESULT FOR startActivityForResult
}
} else {
analysis!!.analyzer = BarcodeZxingAnalyzer { barcode ->
toAddAuth(barcode)
}
}
CameraX.bindToLifecycle(this#ScannerX, preview, analysis)
}
}
override fun onPermissionRationaleShouldBeShown(permissions: MutableList<PermissionRequest>?, token: PermissionToken?) {
//
}
}).check()
}
override fun onResume() {
super.onResume()
textureView.postDelayed({
textureView.systemUiVisibility = flagsFullscreen
}, immersiveFlagTimeout)
}
override fun onDestroy() {
analyzerHandler?.removeCallbacksAndMessages(null)
analyzerHandler?.looper?.quitSafely()
analysis?.analyzer = null
super.onDestroy()
}
I just need to close camera and activity once analyzer gets valid code
ALSO IF I PRESS BACK BUTTON ON SCANNER ACTIVITY CAMERA PREVIEW GOES OFF AND THEN TURN BACK ON AND HAVE TO PRESS THAT BACK BUTTON AGAIN TO FINISH THIS SCANNER ACTIVITY.
SO ITS NOT JUST BARCODE LISTENER. IT HAPPENS WITH BACK BUTTON AS WELL.
Here's a gist of QR Code, let me know if this helps you
// QR Code - XZing
implementation 'com.dlazaro66.qrcodereaderview:qrcodereaderview:2.0.3'
QR Code Gist File
Don't forget to add the permission
<uses-permission android:name="android.permission.CAMERA"/>
Cheers
[SOLVED]
I dont know exactly what causing this issue but somehow this whole thing doesn't work inside an Activity .
CameraX.bindToLifecycle have some strange bug which messing up with Activity LifeCycle.
Implementing everything inside Fragment does solve the issue. Only thing is have to keep all Camera related stuff inside fragment. When its time to finish the activity and return scan results to previous activity better do it in Activity hosting the fragment.
I am trying to create an application to update a persistent notification even while the app is closed. Right now, I'm using a service that is started in MainActivity's onCreate():
serviceIntent = Intent(this, PersistentService::class.java)
val stopped = stopService(serviceIntent)
println("[123] stopped: $stopped")
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
This works perfectly to start it. However, when I reopen and close the app, even though stopped: true is printed, the previous service is still running and the previous service's stopService() was not called.
Stripped down version of the PersistentService class:
var timesStarted = 0
var lastService: PersistentService? = null
class PersistentService: Service(){
private var timer: Timer? = null
private var task: AsyncTask<*, *, *>? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
timesStarted++
println("[123]starting persistent service. timesStarted: $timesStarted lastService===this: ${lastService===this} lastService==this: ${lastService==this} lastService: $lastService")
println("[123] hashCode: ${hashCode()}")
lastService = this
if(timer == null){
timer = Timer()
}
setToLoadingNotification()
timer?.scheduleAtFixedRate(object : TimerTask(){
override fun run(){
println("[123]Updating hashCode: ${this#PersistentService.hashCode()}")
if(task?.status == AsyncTask.Status.RUNNING){
// send notification saying last request timed out
}
task?.cancel(true)
task = DataUpdaterTask(DatabaseDataRequester { GlobalData.connectionProperties }) { dataRequest ->
// send notification based on dataRequest value
}.execute()
}
}, 1000L, UPDATE_PERIOD)
return START_STICKY
}
private fun notify(notification: Notification){
getManager().notify(NOTIFICATION_ID, notification)
startForeground(NOTIFICATION_ID, notification)
}
private fun getBuilder(): Notification.Builder {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
return Notification.Builder(this, NotificationChannels.PERSISTENT_STATUS.id)
}
return Notification.Builder(this)
}
private fun getManager() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override fun stopService(name: Intent?): Boolean {
println("[123]Stopping persistent service")
timer?.cancel() // stop the timer from calling the code any more times
task?.cancel(true) // stop the code from running if it's running
// getManager().cancel(NOTIFICATION_ID)
return super.stopService(name)
}
}
Here is the output
[123] stopped: false
[123]starting persistent service. timesStarted: 1 lastService===this: false lastService==this: false lastService: null
[123] hashCode: 4008007
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
// *close and reopen the app*
[123] stopped: true
[123]starting persistent service. timesStarted: 2 lastService===this: false lastService==this: false lastService: me.retrodaredevil.solarthing.android.PersistentService#3d2847
[123] hashCode: 7823272
[123]Updating hashCode: 7823272
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 7823272
[123]Got successful data request
As you can see from the output, both services are running at the same time. The hashcode shows that they are not the same object so it wouldn't matter if I put my code in the onCreate() instead of onStartCommand() (I already tested this anyway)
It would be helpful if anyone could point me in the right direction. I am new to android development and I had trouble finding the correct way to do this. I'm not even sure if what I'm doing right now is the best way to update the notification.
and the previous service's stopService() was not called.
stopService() is not a lifecycle method of a Service. Perhaps you are thinking of onDestroy().
the previous service is still running
No, the previous service instance was stopped. You just leaked the Timer, because you did not cancel the Timer in onDestroy().
So, override onDestroy(), put your cancel() calls in there, get rid of the rest of stopService(), and you should be in better shape.