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
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).
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,"")
}
}
})
}
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
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
}
}
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