How do i make Android recognize what song is playing? - android

I have a music player but i noticed that android and other devices don't recognize what song is playing.
For example in mi band 4 i can control the music if i have it on youtube music but not in my app or even in my launcher the at a glance widget recognizes it's playing.
Example:
Now that you know the context, what do i need to make it work or what am i missing?
Here's my service that plays the music:
class SimpleMPService: Service() {
private val mBinder = LocalBinder()
private lateinit var notification: Notification
private lateinit var notificationManager: NotificationManager
var playList = ArrayList<Song>()
private var shuffledPlaylist = ArrayList<Song>()
var currentSongPosition: Int = 0
private var currentSongPath: String = ""
var onRepeatMode = false
private lateinit var audioManager: AudioManager
//Listeners
var onMusicSelectedListener: OnMusicSelectedListener? = null
var onMusicSelectedListenerToQueue: OnMusicSelectedListenerToQueue? = null //Since there is no way to have two listeners at same time it needs another listener to the queue list
var onMusicPausedListener: OnMusicPausedListener? = null
var onPlaylistAdded: OnPlaylistsAdded? = null
var onMusicResumedListener: OnMusicResumedListener? = null
var onMusicSecondPassedListener: OnSecondPassedListener? = null
var onMusicShuffleToggledListener: OnMusicShuffleToggledListener? = null
var onMediaPlayerStoppedListener: OnMediaPlayerStoppedListener? = null
//Player States
private var serviceStarted = false
var musicShuffled = false
private var musicStarted = false
//Others
private lateinit var mediaButtonReceiver: ComponentName
private lateinit var mediaSession: MediaSessionCompat
inner class LocalBinder : Binder() {
fun getService(): SimpleMPService = this#SimpleMPService
}
companion object {
private val mediaPlayer = MediaPlayer()
fun startService(context: Context) {
val startIntent = Intent(context, SimpleMPService::class.java)
context.startForegroundService(startIntent)
}
}
override fun onBind(intent: Intent?): IBinder {
val context = this
mediaButtonReceiver = ComponentName(context, ReceiverPlayPause::class.java)
mediaSession = MediaSessionCompat(context, "SessionTag")
mediaSession.setCallback(object : MediaSessionCompat.Callback(){
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
val ke = mediaButtonIntent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
if( ke?.action == KeyEvent.ACTION_DOWN ){
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS )
previousSong( context )
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE )
pauseResumeMusic( context )
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY )
pauseResumeMusic( context )
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_NEXT )
skipSong( context )
}
return super.onMediaButtonEvent(mediaButtonIntent)
}
})
return mBinder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onCreate() {
super.onCreate()
notificationManager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
fun getCurrentPlaylist(): ArrayList<Song>{
return if(!musicShuffled) playList else shuffledPlaylist
}
fun isMusicPlayingOrPaused(): Boolean{ return musicStarted }
fun toggleShuffle(){
if( !musicShuffled ){
musicShuffled = true
shuffledPlaylist = ArrayList()
val tempShuffledPlaylist = ArrayList<Song>()
//Adds the current song to first position
playList.forEach { song ->
if (song.path != currentSongPath)
tempShuffledPlaylist.add(song)
else
shuffledPlaylist.add( song )
}
//Shuffles the temp playlist and adds it to the one with just the current song
tempShuffledPlaylist.shuffle()
for( song in tempShuffledPlaylist )
shuffledPlaylist.add( song )
currentSongPosition = 0
}
else{
musicShuffled = false
for( i in playList.indices ){
if( playList[i].path == currentSongPath ){
currentSongPosition = i
break
}
}
}
onMusicShuffleToggledListener?.onMusicShuffleToggled(musicShuffled)
}
fun enableShuffle(){
musicShuffled = true
shuffledPlaylist = ArrayList(playList)
shuffledPlaylist.shuffle()
onMusicShuffleToggledListener?.onMusicShuffleToggled(true)
currentSongPosition = 0
}
fun setPlaylist( newPlaylist: ArrayList<Song> ){ playList = newPlaylist }
fun playSongAndEnableShuffle(context: Context, position: Int){
val selectedSong = playList[position]
shuffledPlaylist = ArrayList(playList)
shuffledPlaylist.shuffle()
shuffledPlaylist.removeIf{ it.path == selectedSong.path }
shuffledPlaylist.add(0, selectedSong )
currentSongPosition = 0
playSong(context)
musicShuffled = true
}
fun isMusicPlaying(): Boolean{
return mediaPlayer.isPlaying
}
fun getCurrentSongPath(): String{ return currentSongPath }
private val audioFocusChangeListener = OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {}
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT->{}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
if( mediaPlayer.isPlaying )
pauseMusic(this )
}
AudioManager.AUDIOFOCUS_LOSS -> {
if( mediaPlayer.isPlaying )
pauseMusic(this )
}
}
}
private val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setAudioAttributes(AudioAttributes.Builder().run {
setUsage(AudioAttributes.USAGE_MEDIA)
setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
build()
})
setAcceptsDelayedFocusGain(true)
setOnAudioFocusChangeListener(audioFocusChangeListener)
build()
}
fun playSong(context: Context){
serviceStarted = true
musicStarted = true
val songPath: String
val songTitle: String
val songArtist: String
val songID: Long
val songAlbumID: Long
val songAlbumArt: Bitmap
val songDuration: Int
if( !musicShuffled ) {
songPath = playList[currentSongPosition].path
songTitle = playList[currentSongPosition].title
songArtist = playList[currentSongPosition].artistName
songID = playList[currentSongPosition].id
songAlbumID = playList[currentSongPosition].albumID
songAlbumArt = GetSongs.getSongAlbumArt(context, songID, songAlbumID)
songDuration = playList[currentSongPosition].duration
}
else{
songPath = shuffledPlaylist[currentSongPosition].path
songTitle = shuffledPlaylist[currentSongPosition].title
songArtist = shuffledPlaylist[currentSongPosition].artistName
songID = shuffledPlaylist[currentSongPosition].id
songAlbumID = shuffledPlaylist[currentSongPosition].albumID
songAlbumArt = GetSongs.getSongAlbumArt(context, songID, songAlbumID)
songDuration = shuffledPlaylist[currentSongPosition].duration
}
currentSongPath = songPath
val isAudioLimited = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("setting_limitAudioVolume", true)
mediaPlayer.reset()
mediaPlayer.setDataSource(songPath)
when(isAudioLimited){
true-> mediaPlayer.setVolume(0.08F, 0.08F)
false-> mediaPlayer.setVolume(0.1F, 0.1F)
}
mediaPlayer.prepareAsync()
mediaPlayer.setOnPreparedListener {
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
requestPlayWithFocus()
mediaSession.isActive = true
//Open App
val openAppIntent = Intent( context, ActivityMain::class.java )
val pendingOpenAppIntent = TaskStackBuilder.create( context ).run{
addNextIntentWithParentStack(openAppIntent)
getPendingIntent( 0, PendingIntent.FLAG_IMMUTABLE )
}
//Stop Service
val stopIntent = Intent(context, ReceiverStop::class.java )
val pendingStopIntent = PendingIntent.getBroadcast( context, 1, stopIntent, PendingIntent.FLAG_IMMUTABLE )
//Previous Music
val previousSongIntent = Intent(context, ReceiverPreviousSong::class.java )
val pendingPreviousSongIntent = PendingIntent.getBroadcast( context, 1, previousSongIntent, PendingIntent.FLAG_IMMUTABLE )
//Pauses/Plays music
val playPauseIntent = Intent(context, ReceiverPlayPause::class.java )
val pendingPlayPauseIntent = PendingIntent.getBroadcast( context, 1, playPauseIntent, PendingIntent.FLAG_IMMUTABLE )
//Skips to next music
val skipSongIntent = Intent(context, ReceiverSkipSong::class.java )
val pendingSkipSongIntent = PendingIntent.getBroadcast( context, 1, skipSongIntent, PendingIntent.FLAG_IMMUTABLE )
notification = NotificationCompat.Builder(context, "Playback")
.setContentIntent( pendingOpenAppIntent )
.setStyle( androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(1, 2, 3)
)
.setSmallIcon(R.drawable.icon)
.addAction(R.drawable.icon_x, "Stop Player", pendingStopIntent )
.addAction(R.drawable.icon_previous_notification, "Previous Music", pendingPreviousSongIntent )
.addAction(R.drawable.icon_pause_notification, "Play Pause Music", pendingPlayPauseIntent )
.addAction(R.drawable.icon_next_notification, "Next Music", pendingSkipSongIntent )
.build()
mediaSession.setMetadata(
MediaMetadataCompat.Builder()
.putString(MediaMetadata.METADATA_KEY_TITLE, songTitle)
.putString(MediaMetadata.METADATA_KEY_ARTIST, songArtist)
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, songAlbumArt)
.putLong(MediaMetadata.METADATA_KEY_DURATION, songDuration.toLong())
.build()
)
startForeground( 2, notification )
notificationManager.notify( 2, notification )
}
handleSongFinished( context )
if( !musicShuffled ) {
onMusicSelectedListener?.onMusicSelected(playList, currentSongPosition)
onMusicSelectedListenerToQueue?.onMusicSelected(playList, currentSongPosition)
}
else {
onMusicSelectedListener?.onMusicSelected(shuffledPlaylist, currentSongPosition)
onMusicSelectedListenerToQueue?.onMusicSelected(shuffledPlaylist, currentSongPosition)
}
val bluetoothReceiver = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
context.registerReceiver(bluetoothBroadcastReceiver, bluetoothReceiver )
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post( object : Runnable{
override fun run() {
if( onMusicSecondPassedListener != null )
onMusicSecondPassedListener?.onSecondPassed( mediaPlayer.currentPosition )
mainHandler.postDelayed( this,1000)
}
})
}
private val bluetoothBroadcastReceiver = object : BroadcastReceiver(){
override fun onReceive(p0: Context?, p1: Intent?) {
if(isMusicPlaying()) pauseMusic(p0!!)
}
}
fun seekTo( position: Int){
val newSongPosition = position * 1000
mediaPlayer.seekTo(newSongPosition)
if( !mediaPlayer.isPlaying ) mediaPlayer.start()
}
private fun handleSongFinished(context: Context) {
mediaPlayer.setOnCompletionListener{
//If loop mode is activated
if( onRepeatMode ){
playSong( context )
}
//Is it's the last song
else if( (currentSongPosition + 1) == playList.size ){
stopMediaPlayer()
}
else{
currentSongPosition++
playSong( context )
}
}
}
fun toggleLoop(){
onRepeatMode = !onRepeatMode
}
fun stopMediaPlayer(){
onMediaPlayerStoppedListener?.onMediaPlayerStopped()
mediaPlayer.stop()
currentSongPosition = -1
currentSongPath = ""
stopForeground(true)
stopSelf()
}
fun skipSong(context: Context){
if( (currentSongPosition + 1) < playList.size ){
currentSongPosition ++
playSong( context )
}
}
fun previousSong(context: Context){
if( (currentSongPosition - 1) >= 0 ){
currentSongPosition--
playSong( context )
}
}
#Suppress("DEPRECATION")
fun pauseMusic(context: Context ){
val playPauseIcon = R.drawable.icon_play_notification
mediaPlayer.pause()
mediaSession.isActive = false
if( onMusicPausedListener != null)
onMusicPausedListener?.onMusicPaused()
//Updates the notification
val playPauseIntent = Intent(context, ReceiverPlayPause::class.java )
playPauseIntent.putExtra( "action", "playPause" )
val pendingPlayPauseIntent = PendingIntent.getBroadcast( context, 1, playPauseIntent, PendingIntent.FLAG_IMMUTABLE )
notification.actions[2] = Notification.Action( playPauseIcon, "Play Music", pendingPlayPauseIntent )
startForeground( 2, notification )
notificationManager.notify( 2, notification )
}
#Suppress("DEPRECATION")
fun pauseResumeMusic(context: Context ){
val playPauseIcon: Int
if( mediaPlayer.isPlaying ) {
playPauseIcon = R.drawable.icon_play_notification
mediaPlayer.pause()
if( onMusicPausedListener != null) onMusicPausedListener?.onMusicPaused()
}
else {
playPauseIcon = R.drawable.icon_pause_notification
if( onMusicResumedListener != null ) onMusicResumedListener?.onMusicResumed()
requestPlayWithFocus()
}
//Updates the notification
val playPauseIntent = Intent(context, ReceiverPlayPause::class.java )
playPauseIntent.putExtra( "action", "playPause" )
val pendingPlayPauseIntent = PendingIntent.getBroadcast( context, 1, playPauseIntent, PendingIntent.FLAG_IMMUTABLE )
notification.actions[2] = Notification.Action( playPauseIcon, "Play Music", pendingPlayPauseIntent )
startForeground( 2, notification )
notificationManager.notify( 2, notification )
}
private fun requestPlayWithFocus(){
val focusLock = Any()
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
when (res) {
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
mediaPlayer.start()
onMusicResumedListener?.onMusicResumed()
true
}
else -> false
}
}
}
fun updatePlaylists(){ onPlaylistAdded?.onPlaylistAdded() }
//////////////////////////////////////////////////////////////////////////////////////////////////////
interface OnMusicSelectedListener{ fun onMusicSelected(playList: ArrayList<Song>, position: Int ) }
interface OnMusicSelectedListenerToQueue{ fun onMusicSelected(playList: ArrayList<Song>, position: Int ) }
interface OnPlaylistsAdded{ fun onPlaylistAdded() }
interface OnMusicPausedListener{ fun onMusicPaused() }
interface OnMusicResumedListener{ fun onMusicResumed() }
interface OnSecondPassedListener{ fun onSecondPassed(position: Int ) }
interface OnMusicShuffleToggledListener{ fun onMusicShuffleToggled(state: Boolean) }
interface OnMediaPlayerStoppedListener{ fun onMediaPlayerStopped() }
}
I wanted to make other devices recognize what song is playing and map the respective buttons like previous pause play next. I tried to search but didn't find anything useful. I don't know what to search specifically.
Thanks in advance :D

I finally found the problem. I wass missing Playback State And Callbacks.
I am sharing what i did in case anyone ends up here and in the same situation.
First i added other callbacks to media session callback. Mainly this ones:
override fun onPlay() {
super.onPlay()
pauseResumeMusic(context)
}
override fun onStop() {
super.onStop()
stopMediaPlayer()
}
override fun onPause() {
super.onPause()
pauseResumeMusic(context)
}
override fun onSkipToNext() {
super.onSkipToNext()
selectNextSong(context)
}
override fun onSkipToPrevious() {
super.onSkipToPrevious()
selectPreviousSong(context)
}
After that i needed to set the state when those actions happened. For that i made a function to set it.
fun setPlaybackState(state: Int){
val stateBuilder = PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
.apply {
setState(state, mediaPlayer.currentPosition.toLong(), 1.0f)
}
mediaSession.setPlaybackState(stateBuilder.build())
}
Now everytime i need to update i just call the function and send the state as a parameter.
Example:
setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
Hope this helps someone in case it was stuck like me for months XD

Related

Launch new Activity from FrameLayout

I try to study overlay library for kotlin applications https://github.com/KoderLabs/overlay-service. Right now I have a problem related with opening a new activity from a button which is located inside a FrameLayout class.
So, the task is push on button on Main Activity -> open new overlay small window and roll up Main Activity -> push on button inside a small window -> open new Activity.
In this code nothing happens after the clicking on button.
The project include main 3 classes:
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_simple_pip.setOnClickListener {
checkDrawOverlayPermission(IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE)
finishAffinity()
}
}
private fun checkDrawOverlayPermission(code: Int) {
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent, IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE)
} else {
openFloatingWindow(code)
}
} else {
openFloatingWindow(code)
}
}
private fun openFloatingWindow(code: Int) {
when (code) {
IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE -> {
val intent = Intent(this, ImplementPipOverlayService::class.java)
val videoUrl =
"https://s3.amazonaws.com/data.development.momentpin.com/2019/7/3/1562152168485485-0661a550-9d83-11e9-9028-d7af09cf782e.mp4"
val notificationTitle = "Pip Overlay"
val notificationDescription = "Pip overlay description"
val notificationIcon = R.drawable.ic_launcher_foreground
val closeBtnColor = android.R.color.black
val closeBtnBgColor = android.R.color.transparent
intent.putExtra(ImplementPipOverlayService.KEY_STRING_VIDEO_URL, videoUrl)
intent.putExtra(ImplementPipOverlayService.KEY_STRING_NOTIFICATION_DESCRIPTION, notificationDescription)
intent.putExtra(ImplementPipOverlayService.KEY_STRING_NOTIFICATION_TITLE, notificationTitle)
intent.putExtra(ImplementPipOverlayService.KEY_INT_NOTIFICATION_ICON, notificationIcon)
intent.putExtra(ImplementPipOverlayService.KEY_INT_CLOSE_BUTTON_COLOR, closeBtnColor)
intent.putExtra(ImplementPipOverlayService.KEY_INT_CLOSE_BUTTON_BG_COLOR, closeBtnBgColor)
ContextCompat.startForegroundService(this, intent)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE, RESIZEABLE_CUSTOM_WEB_OVERLAY_REQUEST_CODE,
PIP_OVERLAY_REQUEST_CODE, RESIZEABLE_CUSTOM_VIDEO_OVERLAY_REQUEST_CODE -> {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) {
openFloatingWindow(requestCode)
}
} else {
openFloatingWindow(requestCode)
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
companion object {
const val IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE = 251
const val PIP_OVERLAY_REQUEST_CODE = 252
const val RESIZEABLE_CUSTOM_VIDEO_OVERLAY_REQUEST_CODE = 253
const val RESIZEABLE_CUSTOM_WEB_OVERLAY_REQUEST_CODE = 254
}
ImplementPipOverlayService
class ImplementPipOverlayService : PipOverlayService() {
var videoUrl: String? = null
var notificationTitle: String? = null
var notificationDescription: String? = null
var notificationIcon: Int? = null
var closeButtonColor: Int? = null
var closeButtonBg: Int? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
videoUrl = intent?.getStringExtra(KEY_STRING_VIDEO_URL)
notificationTitle = intent?.getStringExtra(KEY_STRING_NOTIFICATION_TITLE)
notificationDescription = intent?.getStringExtra(KEY_STRING_NOTIFICATION_DESCRIPTION)
notificationIcon = intent?.getIntExtra(KEY_INT_NOTIFICATION_ICON, -1)
closeButtonColor = intent?.getIntExtra(KEY_INT_CLOSE_BUTTON_COLOR, -1)
closeButtonBg = intent?.getIntExtra(KEY_INT_CLOSE_BUTTON_BG_COLOR, -1)
return super.onStartCommand(intent, flags, startId)
}
override fun getForegroundNotification(): Notification {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("my_service", "My Background Service")
} else {
packageName
}
var notificationBuilder = NotificationCompat.Builder(this, channelId)
notificationBuilder = notificationTitle?.let {
notificationBuilder.setContentTitle(it)
} ?: run {
notificationBuilder.setContentTitle("Title")
}
notificationDescription?.let {
notificationBuilder = notificationBuilder.setContentText(it)
}
notificationIcon?.let {
notificationBuilder = notificationBuilder.setSmallIcon(it)
}
val notification: Notification = notificationBuilder.build()
return notification
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
override fun getInitialWindowSize(): Point {
return Point(100.toDp(), 100.toDp())
}
override fun getCustomLayoutId(): Int {
return R.layout.pip_layout
}
override fun onServiceRun() {
setOnEventListener(onFullscreen = {
// Not implemented
}, onClosed = {
// Not implemented
})
pipView.removeFullscreenButton()
closeButtonColor?.let {
pipView.getCloseButton().setColorFilter(it)
}
closeButtonBg?.let {
pipView.getCloseButton().setBackgroundColor(it)
}
}
companion object {
const val KEY_STRING_VIDEO_URL = "video_url"
const val KEY_INT_CLOSE_BUTTON_COLOR = "close_button_color"
const val KEY_INT_CLOSE_BUTTON_BG_COLOR = "close_button_background"
const val KEY_STRING_NOTIFICATION_TITLE = "notification_title"
const val KEY_STRING_NOTIFICATION_DESCRIPTION = "notification_description"
const val KEY_INT_NOTIFICATION_ICON = "notification_icon"
}
OverlayPipCustomView
class OverlayPipCustomView : FrameLayout {
private lateinit var constraintsRoot: ConstraintLayout
private lateinit var imageFullscreenButton: ImageView
private lateinit var imageCloseButton: ImageView
private lateinit var customLayoutContent: FrameLayout
private lateinit var customView: View
private lateinit var touchView: View
private lateinit var button: Button
private var playerViewSize: Int = 0
private var sizeChangeable: Boolean = true
private var playerType: Int = 0
private var haveFullscreen = true
/**
* Tracks if viewResizeable is fullscreen.
*/
private var fullscreenOn: Boolean = false
val isDraggable: Boolean
get() {
return !fullscreenOn
}
private var onFullscreen: () -> Unit = {}
private var onClosed: () -> Unit = {}
private var canHideActionButtons = true
private val hideActionHandler = Handler()
private val HIDE_ACTION_DURATION = 2000L
private val hideActionRunnable = Runnable {
if (!isMoving) {
hideActions()
}
}
var isMoving: Boolean = false
constructor(ctx: Context) : super(ctx) {
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
setAttributes(attrs)
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
constructor(ctx: Context, attrs: AttributeSet, defStyle: Int) : super(ctx, attrs, defStyle) {
setAttributes(attrs)
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
private fun setAttributes(attrs: AttributeSet) {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.OverlayCustomView,
0, 0).apply {
try {
playerViewSize = getInteger(R.styleable.OverlayCustomView_ov_player_size, 0)
playerType = getInteger(R.styleable.OverlayCustomView_ov_size_changeable, 0)
sizeChangeable = getBoolean(R.styleable.OverlayCustomView_ov_size_changeable, true)
} finally {
recycle()
}
}
doOnLayout {
startHideAction()
}
}
private fun initView() {
button = findViewById(R.id.button)
constraintsRoot = findViewById(R.id.constraints_root)
imageFullscreenButton = findViewById(R.id.image_screen_action)
imageCloseButton = findViewById(R.id.image_close)
customLayoutContent = findViewById(R.id.custom_view)
touchView = findViewById(R.id.touch_view)
setListeners()
}
fun addCustomView(view: View) {
customLayoutContent.addView(view)
}
fun addCustomView(layoutId: Int) {
customView = inflate(context, layoutId, null)
customLayoutContent.addView(customView)
}
fun getCloseButton() = imageCloseButton
fun getConstraintsRoot() = constraintsRoot
fun getCustomLayoutContent() = customLayoutContent
fun getCustomView() = customView
fun getFullscreenButton() = imageFullscreenButton
fun getTouchView() = touchView
fun removeFullscreenButton() {
haveFullscreen = false
imageFullscreenButton.invisible()
}
private fun setListeners() {
imageFullscreenButton.setOnClickListener {
onFullscreen.invoke()
}
button.setOnClickListener{
println("pressed")
fun alert(context: Context, text: String) {
val intent = Intent(context, MainActivity2::class.java)
intent.putExtra("text", text)
context.startActivity(intent)
}
}
imageCloseButton.setOnClickListener {
onClosed.invoke()
}
}
fun setOnEventActionListener(
onFullscreen: () -> Unit,
onClosed: () -> Unit
) {
this.onFullscreen = onFullscreen
this.onClosed = onClosed
}
private fun startHideAction() {
if (canHideActionButtons) {
hideActionHandler.postDelayed(hideActionRunnable, HIDE_ACTION_DURATION)
}
}
fun restartHideAction() {
hideActionHandler.removeCallbacks(hideActionRunnable)
if (canHideActionButtons) {
hideActionHandler.postDelayed(hideActionRunnable, HIDE_ACTION_DURATION)
}
}
fun hideActions() {
if (canHideActionButtons) {
imageCloseButton.invisible()
if (haveFullscreen) {
imageFullscreenButton.invisible()
}
}
}
fun showActions() {
imageCloseButton.visible()
if (haveFullscreen) {
imageFullscreenButton.visible()
}
}
}
In third class I can't understand what should be placed here to to change initial first Activity to another one
button.setOnClickListener{
println("pressed")
fun alert(context: Context, text: String) {
val intent = Intent(context, MainActivity2::class.java)
intent.putExtra("text", text)
context.startActivity(intent)
}
}
startService(intent) will try and start a new Service and unfortunately does not throw an error if you call it with an Activity class.
To launch MainActivity2 instead call context.startActivity(intent).

Android Notification is not canceling after workManager completes

I am using Android WorkManager with setForegroundAsync, But the notification is not canceling after workManager completes.
OfflinePushWorkManager is a work manager class to push offline stuff to the server.
class OfflinePushWorkManager(var context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams),
KoinComponent {
private val reflectionUtil: ReflectionUtil by inject()
private val apiService: ApiService by inject()
private val flowchartDao: FlowChartDao by inject()
val flowchartUploadObserver = MutableLiveData<ApiResponse<BaseResponse<SaveFlowchartResponse>>>()
val deleteFlowchart = MutableLiveData<ApiResponse<Unit>>()
override suspend fun doWork(): Result {
setForegroundAsync(createForegroundInfo())
val data = inputData.getString(Constants.BundleKeys.FLOWCHART)
// val docString : String? = DocumentReaderUtil.readTxtFromUri(Uri.parse(data.toString()), applicationContext)
Log.d("uri is ", data.toString())
val dataString: String? = AppUtils.readStringFromFileInternalStorage(applicationContext, data)
Log.d("dataString is ", dataString+"")
dataString?.let {
val flowchart = reflectionUtil.gsonToClass(it, FlowChart::class.java)
uploadFlowchart(flowchart)
AppLogs.e("classObject", "" + flowchart)
}
return Result.success()
}
companion object{
var notificationManager:NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
var uploadedProgress : Int = 1
var notification: NotificationCompat.Builder? = null
fun updateProgress(progress : Int?){
if(progress == 100){
uploadedProgress = 100
cancelNotification()
AppLogs.e("uploadedProgress", " " + uploadedProgress)
}
else {
uploadedProgress = uploadedProgress + 30
if(uploadedProgress < 100 && uploadedProgress < 70)
notification?.setProgress(100,uploadedProgress,false)
AppLogs.e("uploadedProgress", " " + uploadedProgress)
if(notification != null) {
Log.d("Notification CancelAll---->", "5 Notifying")
notificationManager.notify(1, notification?.build())
}
}
}
fun cancelNotification(){
Log.d("Notification CancelAll---->", "3")
notificationManager.cancelAll()
}
fun displayNotification(title: String) : Notification {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
Constants.SCULPTOR,
Constants.SCULPTOR,
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager?.createNotificationChannel(channel)
}
notification = NotificationCompat.Builder(
context,
Constants.SCULPTOR
)
.setContentTitle(title)
.setSmallIcon(R.mipmap.ic_launcher)
.setOnlyAlertOnce(true)
notification?.setProgress(100,uploadedProgress,false)
Log.d("Notification CancelAll---->", "4 Notifying")
notificationManager?.notify(1, notification?.build())
return notification!!.build()
}
}
/**
* Create ForegroundInfo required to run a Worker in a foreground service.
*/
private fun createForegroundInfo(): ForegroundInfo {
val notificationId = 1
return ForegroundInfo(notificationId, displayNotification("Uploading in progress...."))
}
}
Calling Sequense
private fun setWorkManager(list: Array<FlowChart>) {
Log.d("---->", "${list.size} {list.size}")
val gsonList:ArrayList<String> = ArrayList()
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
workManager = WorkManager.getInstance(applicationContext)
OfflinePushWorkManager.uploadedProgress = 0
for( i in 0 until list.size) {
val listData = reflectionUtil.gsonFromClass(list.get(i))
gsonList.add(listData)
if(mPref.get(PreferenceConstants.TOKEN,"").isNullOrEmpty()){
workManager?.cancelAllWork()
OfflinePushWorkManager.cancelNotification()
break
}
val dataString: String = AppUtils.writeStringToFileInternalStorage(applicationContext, listData, i.toString())
val data: Data = Data.Builder().putString(Constants.BundleKeys.FLOWCHART,dataString).build()
val workManagerRequest = OneTimeWorkRequest.Builder(OfflinePushWorkManager::class.java).setInputData(data)
.setConstraints(constraints).build()
workManager?.enqueue(workManagerRequest)
mPref.put(PreferenceConstants.WORK_ID,workManagerRequest.id.toString())
handleWorkManagerState(workManagerRequest,i,list)
}
}
private fun cancelNotification(){
workManager?.pruneWork()
GlobalScope.launch {
val nMgr = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
nMgr.activeNotifications.forEach {
nMgr.cancel(it.id)
Log.d("Notification Cancel---->", "1 ${it.id}")
}
Log.d("Notification CancelAll---->", "2")
nMgr.cancelAll()
}
}
private fun handleWorkManagerState(workManagerRequest: OneTimeWorkRequest, i: Int, list: Array<FlowChart>) {
workManager?.getWorkInfoByIdLiveData(workManagerRequest.id)
?.observe(this, object : androidx.lifecycle.Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
if(t?.state == WorkInfo.State.RUNNING ){
mPref.put(PreferenceConstants.IS_WORK_RUNNING,true)
OfflinePushWorkManager.updateProgress(0)
} else if(t?.state == WorkInfo.State.SUCCEEDED){
if(i+1 == list.size){
mPref.put(PreferenceConstants.IS_WORK_RUNNING,false)
OfflinePushWorkManager.updateProgress(100)
workManager?.cancelAllWork()
OfflinePushWorkManager.cancelNotification()
cancelNotification()
} else{
OfflinePushWorkManager.updateProgress(0)
}
} else if(t?.state == WorkInfo.State.FAILED){
if(i+1 == list.size) {
mPref.put(PreferenceConstants.IS_WORK_RUNNING,false)
workManager?.cancelAllWork()
OfflinePushWorkManager.cancelNotification()
cancelNotification()
}
} else if(t?.state == WorkInfo.State.CANCELLED){
mPref.put(PreferenceConstants.IS_WORK_RUNNING,false)
workManager?.cancelAllWork()
OfflinePushWorkManager.cancelNotification()
cancelNotification()
mPref.put(PreferenceConstants.WORK_ID,"")
}
}
})
}

How can I make Exoplayer2 run in background? (Kotlin)

I am a noob in Kotlin.
I've made a streaming music player using Exoplayer2.
For running this music player in background, I tried to bind activity to service.
But it is not working.
I dont know why this player is stopped outside the app even though i add the foreground service.
Can anybody help me? :(
MusicService.kt
var mExoPlayer: SimpleExoPlayer? = null
class MusicService : MediaBrowserServiceCompat() {
private var mMediaSession: MediaSessionCompat? = null
private lateinit var mStateBuilder: PlaybackStateCompat.Builder
private var playbackPosition = 0L
private var currentWindow = 0
private var oldUri: Uri? = null
private val mMediaSessionCallback = object : MediaSessionCompat.Callback() {
override fun onPlayFromUri(uri: Uri?, extras: Bundle?) {
super.onPlayFromUri(uri, extras)
uri?.let {
val mediaSource = extractMediaSourceFromUri(uri)
if (uri != oldUri)
play(mediaSource)
else play()
oldUri = uri
}
}
override fun onPause() {
super.onPause()
pause()
}
override fun onStop() {
super.onStop()
stop()
}
}
override fun onCreate() {
super.onCreate()
initializePlayer()
initializeExtractor()
initializeAttributes()
mMediaSession = MediaSessionCompat(baseContext, "tag for debugging").apply {
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS )
mStateBuilder = PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE)
setPlaybackState(mStateBuilder.build())
setCallback(mMediaSessionCallback)
setSessionToken(sessionToken)
isActive = true
}
}
private var mAttrs: AudioAttributes? = null
private fun play(mediaSource: MediaSource) {
if (mExoPlayer == null) initializePlayer()
mExoPlayer?.apply {
mAttrs?.let { initializeAttributes() }
mAttrs?.let { setAudioAttributes(it, true) }
prepare(mediaSource)
seekTo(currentWindow, playbackPosition)
playWhenReady = true
}
}
private fun play() {
mExoPlayer?.apply {
mExoPlayer?.playWhenReady = true
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
mMediaSession?.isActive = true
}
}
private fun initializePlayer() {
mExoPlayer = ExoPlayerFactory.newSimpleInstance(
this, DefaultRenderersFactory(baseContext)
, DefaultTrackSelector(),
DefaultLoadControl()
)
}
private fun pause() {
mExoPlayer?.apply {
playWhenReady = false
if (playbackState == PlaybackStateCompat.STATE_PLAYING) {
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
}
}
}
private fun stop() {
mExoPlayer?.playWhenReady = false
mExoPlayer?.release()
mExoPlayer = null
updatePlaybackState(PlaybackStateCompat.STATE_NONE)
mMediaSession?.isActive = false
mMediaSession?.release()
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
stopSelf()
}
override fun onDestroy() {
super.onDestroy()
stop()
}
private fun updatePlaybackState(state: Int) {
mMediaSession?.setPlaybackState(
PlaybackStateCompat.Builder().setState(
state
, 0L
, 1.0f // Speed playing
).build()
)
}
private fun initializeAttributes() {
mAttrs = AudioAttributes.Builder().setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build()
}
private lateinit var mExtractorFactory: ExtractorMediaSource.Factory
private fun initializeExtractor() {
val userAgent = Util.getUserAgent(baseContext, "Application Name")
mExtractorFactory = ExtractorMediaSource.Factory(DefaultDataSourceFactory(this, userAgent))
.setExtractorsFactory(DefaultExtractorsFactory())
}
private fun extractMediaSourceFromUri(uri: Uri): MediaSource {
return mExtractorFactory.createMediaSource(uri)
}
override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
}
override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
return BrowserRoot("", null)
}
playerAct.kt
var audioUrlPass = ""
class playerAct: AppCompatActivity() { private val songUrl: String = audioUrlPass
private lateinit var mMediaBrowserCompat: MediaBrowserCompat
private val connectionCallback: MediaBrowserCompat.ConnectionCallback = object : MediaBrowserCompat.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
mMediaBrowserCompat.sessionToken.also { token ->
val mediaController = MediaControllerCompat(this#playerAct, token)
MediaControllerCompat.setMediaController(this#playerAct, mediaController)
}
playPauseBuild()
}
override fun onConnectionFailed() {
super.onConnectionFailed()
}
}
private val mControllerCallback = object : MediaControllerCompat.Callback() {
}
fun playPauseBuild() {
val mediaController = MediaControllerCompat.getMediaController(this#playerAct)
main_pcv.showTimeoutMs = 0
exo_play.setOnClickListener {
val state = mediaController.playbackState.state
if (state == PlaybackStateCompat.STATE_PAUSED ||
state == PlaybackStateCompat.STATE_STOPPED ||
state == PlaybackStateCompat.STATE_NONE
) {
mediaController.transportControls.playFromUri(Uri.parse(songUrl), null)
}
else if (state == PlaybackStateCompat.STATE_PLAYING ||
state == PlaybackStateCompat.STATE_BUFFERING ||
state == PlaybackStateCompat.STATE_CONNECTING
) {
mediaController.transportControls.pause()
}
}
mediaController.registerCallback(mControllerCallback)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.player_layout)
val componentName = ComponentName(this, MusicService::class.java)
mMediaBrowserCompat = MediaBrowserCompat(
this, componentName, //Identifier for the service
connectionCallback,
null
)
menuButton.setOnClickListener {
super.onBackPressed()
}
btnTime1.setOnClickListener {
val intent = Intent(this, TimerActivity::class.java)
startActivity(intent)
}
playerSeekBar.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekbar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser){
val volumeNum = progress / 100.0f
mExoPlayer!!.setVolume(volumeNum)
}
}
override fun onStartTrackingTouch(seekbar: SeekBar?) { }
override fun onStopTrackingTouch(seekbar: SeekBar?) { }
})
}
override fun onStart() {
super.onStart()controller
Intent(this, MusicService::class.java).also {
mMediaBrowserCompat.connect()
}
}
override fun onStop() {
super.onStop()
val controllerCompat = MediaControllerCompat.getMediaController(this)
controllerCompat?.unregisterCallback(mControllerCallback)
mMediaBrowserCompat.disconnect()
}
}
In order to start the foreground service, you will need a notification shown within 5 seconds of starting it. Check the following guide in the android documentation: https://developer.android.com/guide/topics/media-apps/audio-app/building-a-mediabrowserservice#mediastyle-notifications

Service does not work when the screen is off

I have a function which is responsible for detecting my location along with the location of some records that I have in the realm of my database. I calculate a range of approximately 30 meters. If my location is close to the range, it will be saved as a record in another Realm class.
All good until there, but when the phone's screen is turned off, it stops working.
I don't know how to make it work, I need to keep recording while the screen is off. I use a timer in the service. I was investigating what WorkManager is, but I don't think that's what I need.
Can you help me how to improve my code?
Thank you!
class DistanceService : Service() {
private val timer = Timer()
override fun onBind(intent: Intent): IBinder? {
Log.i("service", "Close DistanceService2")
return null
}
override fun onCreate() {
Log.i("service", "Iniciando DistanceService")
super.onCreate()
}
private fun startService() {
stopService(Intent(this, AlertRepartoSleepService::class.java))
timer.scheduleAtFixedRate(mainTask(), 0, 2000)
}
private inner class mainTask : TimerTask() {
override fun run() {
toastHandler.sendEmptyMessage(0)
}
}
private val toastHandler = #SuppressLint("HandlerLeak")
object : Handler() {
override fun handleMessage(msg: Message) {
val gps = Gps(this#DistanceService)
if (gps.isLocationEnabled()) {
if (gps.latitude.toString() != "0.0" || gps.longitude.toString() != "0.0") {
distance(this#DistanceService, gps.latitude.toString(), gps.longitude.toString())
val toast = Toast.makeText(applicationContext, "Rango SuministroReparto", Toast.LENGTH_SHORT)
toast.setGravity(Gravity.TOP, Gravity.CENTER, 0)
toast.show()
} else {
Util.toastMensaje(applicationContext, "Gps Sin Cobertura")
}
}
}
}
override fun onDestroy() {
timer.cancel()
Log.i("service", "Close DistanceService")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startService()
return START_STICKY
}
private fun distance(context: Context, latitud: String, longitud: String) {
Completable.fromAction {
Realm.getDefaultInstance().use { realm ->
val suministroImp: SuministroImplementation = SuministroOver(realm)
val registroImp: RegistroImplementation = RegistroOver(realm)
val result: RealmResults<SuministroReparto>? = suministroImp.getSuministroReparto(1)
if (result != null) {
if (result.size != 0) {
for (r: SuministroReparto in result) {
if (r.latitud.isNotEmpty() && r.longitud.isNotEmpty()) {
val l1 = Location("location 1")
l1.latitude = r.latitud.toDouble()
l1.longitude = r.longitud.toDouble()
val l2 = Location("location 2")
l2.latitude = latitud.toDouble()
l2.longitude = longitud.toDouble()
try {
val distancia = calculationByDistance(l1, l2)
if (distancia <= 15) {
if (r.foto_Reparto != 0) {
val repartoId = registroImp.getRegistroIdentity()
if (r.foto_Reparto != 2) {
suministroImp.repartoSaveService(registroImp.getRegistroIdentity(), r.id_Reparto, r.id_Operario_Reparto, Util.getFechaActual(), latitud, longitud, r.id_observacion.toString(), 0)
}
context.startService(Intent(context, AlertRepartoSleepService::class.java)
.putExtra("Cod_Orden_Reparto", r.Cod_Orden_Reparto)
.putExtra("id_cab_Reparto", r.id_Reparto)
.putExtra("direction", r.Direccion_Reparto)
.putExtra("suministroNumeroReparto", r.Suministro_Numero_reparto)
.putExtra("foto", r.foto_Reparto)
.putExtra("operarioId", r.id_Operario_Reparto)
.putExtra("cliente", r.Cliente_Reparto)
.putExtra("registroId", repartoId))
stopSelf()
break
} else {
suministroImp.repartoSaveService(registroImp.getRegistroIdentity(), r.id_Reparto, r.id_Operario_Reparto, Util.getFechaActual(), latitud, longitud, r.id_observacion.toString(), 1)
}
}
} catch (e: Exception) {
Util.toastMensaje(applicationContext, e.toString())
}
}
}
} else {
notification(this#DistanceService)
}
}
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CompletableObserver {
override fun onComplete() {
Log.i("TAG", "COMPLETADO")
}
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
Log.i("TAG", e.toString())
}
})
}
private fun calculationByDistance(StartP: Location, EndP: Location): Double {
val radius = 6371 * 1000
val lat1 = StartP.latitude
val lat2 = EndP.latitude
val lon1 = StartP.longitude
val lon2 = EndP.longitude
val dLat = Math.toRadians(lat2 - lat1)
val dLon = Math.toRadians(lon2 - lon1)
val a = sin(dLat / 2) * sin(dLat / 2) + (cos(Math.toRadians(lat1))
* cos(Math.toRadians(lat2)) * sin(dLon / 2)
* sin(dLon / 2))
val c = 2 * asin(sqrt(a))
val valueResult = radius * c
val m = valueResult / 1
val newFormat = DecimalFormat("####")
val mInDec = Integer.valueOf(newFormat.format(m))
return mInDec.toDouble()
}
private fun getBasicNotificationBuilder(context: Context, channelId: String, playSound: Boolean)
: NotificationCompat.Builder {
val notificationSound: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val nBuilder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
.setAutoCancel(true)
.setDefaults(0)
if (playSound) nBuilder.setSound(notificationSound)
return nBuilder
}
private fun notification(context: Context) {
val intent = Intent(context, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val nBuilder = getBasicNotificationBuilder(context, CHANNEL_ID_TIMER, false)
nBuilder.setContentTitle(String.format("Reparto"))
.setContentText("Acabas de culminar tus repartos . Tomar selfie FIN DE TRABAJO")
.setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
val nManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(CHANNEL_ID_TIMER, CHANNEL_NAME_TIMER, true)
nManager.notify(1, nBuilder.build())
}
#TargetApi(26)
private fun NotificationManager.createNotificationChannel(channelID: String,
channelName: String,
playSound: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channelImportance = if (playSound) NotificationManager.IMPORTANCE_DEFAULT
else NotificationManager.IMPORTANCE_LOW
val nChannel = NotificationChannel(channelID, channelName, channelImportance)
nChannel.enableLights(true)
nChannel.lightColor = Color.BLUE
this.createNotificationChannel(nChannel)
}
}
companion object {
private const val CHANNEL_ID_TIMER = "enable_gps"
private const val CHANNEL_NAME_TIMER = "Dsige_Enable_Gps"
private const val TIMER_ID = 0
}
}

How to send data from Activity to Widget?

Im newbie Android(Kotlin), I want send data from my app to widget and show on home screen. Thank you so much. Sorry for bad english.
In MainActivity class, I am using Room to save it.
private fun saveNewCityCollection(informationWeather: InformationWeather, state: Boolean = Constants.OTHER_LOCATION) {
val weatherRepository = WeatherRepository(applicationContext)
val cityCollection = CityCollection()
cityCollection.cityName = informationWeather.location.name
cityCollection.countryName = informationWeather.location.country
cityCollection.temp = informationWeather.current.temp
cityCollection.appTemp = informationWeather.current.appTemp
cityCollection.humidity = informationWeather.current.humidity
cityCollection.wind = informationWeather.current.windSpeed
cityCollection.cloud = informationWeather.current.cloud
cityCollection.description = informationWeather.current.condition.description
cityCollection.icon = informationWeather.current.condition.icon
cityCollection.date = informationWeather.current.date
cityCollection.day = informationWeather.current.isDay
if (location == cityCollection.cityName) {
cityCollection.state = true
}
if (state) {
cityCollection.state = true
val editor = getSharedPreferences(getString(R.string.shared_preference_name), Context.MODE_PRIVATE).edit()
editor.putString(Constants.NAME_LOCATION, cityCollection.cityName)
editor.apply()
}
weatherRepository.insert(cityCollection, this)
}
And my model: CityColection, I am using Parcelable.
class CityCollection() : Parcelable {
#PrimaryKey
#NonNull
#ColumnInfo(name = "cityname")
lateinit var cityName: String
lateinit var countryName: String
var state = Constants.OTHER_LOCATION
var temp: Float = 0F
var appTemp: Float = 0F
var humidity: Int = 0
var wind: Float = 0F
var cloud: Int = 0
var day: Int = 0
lateinit var description: String
var icon: String = "na"
var date: String = "dd/mm/yy"
constructor(parcel: Parcel) : this() {
cityName = parcel.readString()
countryName = parcel.readString()
state = parcel.readByte() != 0.toByte()
temp = parcel.readFloat()
appTemp = parcel.readFloat()
humidity = parcel.readInt()
wind = parcel.readFloat()
cloud = parcel.readInt()
day = parcel.readInt()
description = parcel.readString()
icon = parcel.readString()
date = parcel.readString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(cityName)
parcel.writeString(countryName)
parcel.writeByte(if (state) 1 else 0)
parcel.writeFloat(temp)
parcel.writeFloat(appTemp)
parcel.writeInt(humidity)
parcel.writeFloat(wind)
parcel.writeInt(cloud)
parcel.writeInt(day)
parcel.writeString(description)
parcel.writeString(icon)
parcel.writeString(date)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CityCollection> {
override fun createFromParcel(parcel: Parcel): CityCollection {
return CityCollection(parcel)
}
override fun newArray(size: Int): Array<CityCollection?> {
return arrayOfNulls(size)
}
}
}
In My Fragment, I am getParcelable to get data.
private fun initData() {
val bundle = arguments
if (bundle != null) {
val cityCollection: CityCollection = bundle.getParcelable(Constants.CITY_COLLECTION)
tvWind.text = cityCollection.wind.toInt().toString() + " km/h"
}
}
And my ViewPagerAdapter:
class ViewPagerAdapter(fm: FragmentManager, private var listCityCollection: MutableList<CityCollection>)
: FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment? {
return newFragment(listCityCollection[position])
}
override fun getItemPosition(`object`: Any): Int {
return PagerAdapter.POSITION_NONE
}
override fun getCount(): Int {
return listCityCollection.size
}
private fun newFragment(cityCollection: CityCollection): FragmentShowWeatherForecast {
val fragmentShowWeatherForecast = FragmentShowWeatherForecast()
val bundle = Bundle()
bundle.putParcelable(Constants.CITY_COLLECTION, cityCollection)
fragmentShowWeatherForecast.arguments = bundle
return fragmentShowWeatherForecast
}
}
My Widget class:
class WeatherWidget : AppWidgetProvider() {
override fun onEnabled(context: Context?) {
super.onEnabled(context)
val intent = Intent(context, WeatherWidget::class.java)
intent.action = Constants.UPDATE_WIDGET
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
val alarmManager = context?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), (Constants.UPDATE_INTERVAL * 1000).toLong(), pendingIntent)
}
#SuppressLint("SimpleDateFormat")
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
if (appWidgetIds != null) {
for (i in appWidgetIds.indices) {
val views = RemoteViews(context?.packageName, R.layout.widget_weather)
val openApp = Intent(context, MainActivity::class.java)
val pIntent = PendingIntent.getActivity(context, 0, openApp, 0)
views.setOnClickPendingIntent(R.id.rlWidget, pIntent)
appWidgetManager?.updateAppWidget(appWidgetIds[i], views)
}
}
}
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
super.onDeleted(context, appWidgetIds)
Toast.makeText(context, "onDeleted()", Toast.LENGTH_LONG).show()
}
override fun onDisabled(context: Context?) {
super.onDisabled(context)
val intent = Intent(context, WeatherWidget::class.java)
intent.action = Constants.UPDATE_WIDGET
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
val alarmManager = context?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pendingIntent)
}
}
My solution is save your model as json in SharedPreferences, some thing like below
preferenceApi.put(PREFS_WEATHER_DATA + appWidgetId,
new GsonBuilder().create().toJson(data, WeatherData.class));
and get it in onUpdate() of Provider of Widget
SharedPreferenceApi preferenceApi = new SharedPreferenceApi(context);
Gson gson = new Gson();
String jsonData = preferenceApi.get(PREFS_WEATHER_DATA + widgetId, String.class);
WeatherData weatherData = gson.fromJson(jsonData, WeatherData.class);
hope this helps

Categories

Resources