From f45e57009750b4fdc87d675120f44a2998623bfd Mon Sep 17 00:00:00 2001 From: Prathamesh More Date: Thu, 27 Jan 2022 17:58:15 +0530 Subject: [PATCH] Converted MusicService to Kotlin --- .../base/AbsMusicServiceActivity.kt | 9 +- .../AppShortcutLauncherActivity.kt | 6 +- .../retromusic/appwidgets/AppWidgetBig.kt | 4 +- .../retromusic/appwidgets/AppWidgetCard.kt | 4 +- .../retromusic/appwidgets/AppWidgetCircle.kt | 4 +- .../retromusic/appwidgets/AppWidgetClassic.kt | 4 +- .../retromusic/appwidgets/AppWidgetMD3.kt | 4 +- .../retromusic/appwidgets/AppWidgetSmall.kt | 4 +- .../retromusic/appwidgets/AppWidgetText.kt | 4 +- .../appwidgets/base/BaseAppWidget.kt | 6 +- .../retromusic/dialogs/SleepTimerDialog.kt | 4 +- .../player/gradient/GradientPlayerFragment.kt | 2 +- .../service/MediaButtonIntentReceiver.kt | 7 +- .../service/MediaSessionCallback.kt | 7 +- .../retromusic/service/MusicService.java | 1695 ----------------- .../monkey/retromusic/service/MusicService.kt | 1539 +++++++++++++++ .../retromusic/service/QueueSaveHandler.kt | 2 +- .../service/ThrottledSeekHandler.kt | 3 +- .../PlayingNotificationClassic.kt | 5 +- .../notification/PlayingNotificationImpl24.kt | 6 +- 20 files changed, 1600 insertions(+), 1719 deletions(-) delete mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MusicService.java create mode 100644 app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt index 1ab1e4998..4c70a87bd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/base/AbsMusicServiceActivity.kt @@ -24,12 +24,17 @@ import code.name.monkey.retromusic.db.toPlayCount import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.interfaces.IMusicServiceEventListener import code.name.monkey.retromusic.repository.RealRepository -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.MEDIA_STORE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.QUEUE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.REPEAT_MODE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_CHANGED import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import java.lang.ref.WeakReference -import java.util.* abstract class AbsMusicServiceActivity : AbsBaseActivity(), IMusicServiceEventListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt index 6ecd760d5..b163c1c8d 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.kt @@ -26,7 +26,11 @@ import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY_PLAYLIST +import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_PLAYLIST +import code.name.monkey.retromusic.service.MusicService.Companion.INTENT_EXTRA_SHUFFLE_MODE +import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_NONE +import code.name.monkey.retromusic.service.MusicService.Companion.SHUFFLE_MODE_SHUFFLE class AppShortcutLauncherActivity : Activity() { diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt index 8cf54daa4..2e9112ad5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt @@ -31,7 +31,9 @@ import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt index b1ba48000..092db34ed 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt @@ -32,7 +32,9 @@ import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt index 5bd4f6bbf..f89319eea 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCircle.kt @@ -30,8 +30,8 @@ import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.ACTION_TOGGLE_PAUSE -import code.name.monkey.retromusic.service.MusicService.TOGGLE_FAVORITE +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE +import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_FAVORITE import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt index b0740a3af..535d6f54b 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt @@ -33,7 +33,9 @@ import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt index 6a468be0c..124bdff5c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetMD3.kt @@ -32,7 +32,9 @@ import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.DensityUtil import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.PreferenceUtil diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt index 073691eff..6c497514f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt @@ -32,7 +32,9 @@ import code.name.monkey.retromusic.glide.GlideApp import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import com.bumptech.glide.Glide diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt index 248e53ea9..9b7d46928 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt @@ -28,7 +28,9 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt index 0e1508757..48f3a9dc1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt @@ -33,7 +33,11 @@ import code.name.monkey.retromusic.App import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.APP_WIDGET_UPDATE +import code.name.monkey.retromusic.service.MusicService.Companion.EXTRA_APP_WIDGET_NAME +import code.name.monkey.retromusic.service.MusicService.Companion.FAVORITE_STATE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.META_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED abstract class BaseAppWidget : AppWidgetProvider() { diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt index 290004ea3..ffd869495 100755 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.kt @@ -35,8 +35,8 @@ import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT -import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PENDING_QUIT +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_QUIT import code.name.monkey.retromusic.util.PreferenceUtil import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.WhichButton diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt index 7141f3a50..a3e2a4c1e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/player/gradient/GradientPlayerFragment.kt @@ -545,7 +545,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0) } - fun setUpProgressSlider() { + private fun setUpProgressSlider() { binding.playbackControlsFragment.progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.kt b/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.kt index 848b21105..54e3e5e4e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.kt @@ -27,7 +27,12 @@ import android.util.Log import android.view.KeyEvent import androidx.core.content.ContextCompat import code.name.monkey.retromusic.BuildConfig -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PAUSE +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_PLAY +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_STOP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE /** diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt b/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt index 360c6dd6c..b52752be2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/MediaSessionCallback.kt @@ -28,12 +28,13 @@ import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.repository.* -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.CYCLE_REPEAT +import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_FAVORITE +import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_SHUFFLE import code.name.monkey.retromusic.util.MusicUtil import org.koin.core.component.KoinComponent import org.koin.core.component.inject - /** * Created by hemanths on 2019-08-01. */ @@ -95,7 +96,7 @@ class MediaSessionCallback( AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_HISTORY -> topPlayedRepository.recentlyPlayedTracks() AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_SUGGESTIONS -> topPlayedRepository.recentlyPlayedTracks() AutoMediaIDHelper.MEDIA_ID_MUSICS_BY_TOP_TRACKS -> topPlayedRepository.recentlyPlayedTracks() - else -> musicService.playingQueue as List + else -> musicService.playingQueue } songs.addAll(tracks) var songIndex = MusicUtil.indexOfSongInList(tracks, itemId) diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java deleted file mode 100644 index f6a246610..000000000 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ /dev/null @@ -1,1695 +0,0 @@ -/* - * Copyright (c) 2019 Hemanth Savarala. - * - * Licensed under the GNU General Public License v3 - * - * This is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by - * the Free Software Foundation either version 3 of the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - */ - -package code.name.monkey.retromusic.service; - -import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE; -import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT; -import static org.koin.java.KoinJavaComponent.get; -import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN; -import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART; -import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION; -import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION; -import static code.name.monkey.retromusic.ConstantsKt.PLAYBACK_SPEED; -import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET; -import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.bluetooth.BluetoothDevice; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.ServiceInfo; -import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.media.audiofx.AudioEffect; -import android.os.Binder; -import android.os.Build; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.PowerManager; -import android.os.Process; -import android.provider.MediaStore; -import android.support.v4.media.MediaBrowserCompat; -import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.media.MediaBrowserServiceCompat; -import androidx.preference.PreferenceManager; - -import com.bumptech.glide.RequestBuilder; -import com.bumptech.glide.request.target.SimpleTarget; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Random; - -import code.name.monkey.appthemehelper.util.VersionUtils; -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.activities.LockScreenActivity; -import code.name.monkey.retromusic.appwidgets.AppWidgetBig; -import code.name.monkey.retromusic.appwidgets.AppWidgetCard; -import code.name.monkey.retromusic.appwidgets.AppWidgetCircle; -import code.name.monkey.retromusic.appwidgets.AppWidgetClassic; -import code.name.monkey.retromusic.appwidgets.AppWidgetMD3; -import code.name.monkey.retromusic.appwidgets.AppWidgetSmall; -import code.name.monkey.retromusic.appwidgets.AppWidgetText; -import code.name.monkey.retromusic.auto.AutoMediaIDHelper; -import code.name.monkey.retromusic.auto.AutoMusicProvider; -import code.name.monkey.retromusic.glide.BlurTransformation; -import code.name.monkey.retromusic.glide.GlideApp; -import code.name.monkey.retromusic.glide.RetroGlideExtension; -import code.name.monkey.retromusic.helper.MusicPlayerRemote; -import code.name.monkey.retromusic.helper.ShuffleHelper; -import code.name.monkey.retromusic.model.Song; -import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; -import code.name.monkey.retromusic.providers.HistoryStore; -import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore; -import code.name.monkey.retromusic.providers.SongPlayCountStore; -import code.name.monkey.retromusic.service.notification.PlayingNotification; -import code.name.monkey.retromusic.service.notification.PlayingNotificationClassic; -import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl24; -import code.name.monkey.retromusic.service.playback.Playback; -import code.name.monkey.retromusic.util.MusicUtil; -import code.name.monkey.retromusic.util.PackageValidator; -import code.name.monkey.retromusic.util.PreferenceUtil; -import code.name.monkey.retromusic.util.RetroUtil; -import code.name.monkey.retromusic.volume.AudioVolumeObserver; -import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener; -import kotlin.Unit; - -/** - * @author Karim Abou Zeid (kabouzeid), Andrew Neal - */ -public class MusicService extends MediaBrowserServiceCompat - implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks, OnAudioVolumeChangedListener { - - public static final String TAG = MusicService.class.getSimpleName(); - public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; - public static final String MUSIC_PACKAGE_NAME = "com.android.music"; - public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause"; - public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play"; - public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist"; - public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause"; - public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop"; - public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip"; - public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind"; - public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice"; - public static final String ACTION_PENDING_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".pendingquitservice"; - public static final String INTENT_EXTRA_PLAYLIST = - RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"; - public static final String INTENT_EXTRA_SHUFFLE_MODE = - RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode"; - public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate"; - public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"; - // Do not change these three strings as it will break support with other apps (e.g. last.fm - // scrobbling) - public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged"; - public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged"; - public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged"; - - public static final String FAVORITE_STATE_CHANGED = - RETRO_MUSIC_PACKAGE_NAME + "favoritestatechanged"; - public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged"; - public static final String SHUFFLE_MODE_CHANGED = - RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged"; - public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged"; - public static final String CYCLE_REPEAT = RETRO_MUSIC_PACKAGE_NAME + ".cyclerepeat"; - public static final String TOGGLE_SHUFFLE = RETRO_MUSIC_PACKAGE_NAME + ".toggleshuffle"; - public static final String TOGGLE_FAVORITE = RETRO_MUSIC_PACKAGE_NAME + ".togglefavorite"; - public static final String SAVED_POSITION = "POSITION"; - public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; - public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; - public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; - public static final int RELEASE_WAKELOCK = 0; - public static final int TRACK_ENDED = 1; - public static final int TRACK_WENT_TO_NEXT = 2; - public static final int PLAY_SONG = 3; - public static final int PREPARE_NEXT = 4; - public static final int SET_POSITION = 5; - public static final int FOCUS_CHANGE = 6; - public static final int DUCK = 7; - public static final int UNDUCK = 8; - public static final int RESTORE_QUEUES = 9; - public static final int SHUFFLE_MODE_NONE = 0; - public static final int SHUFFLE_MODE_SHUFFLE = 1; - public static final int REPEAT_MODE_NONE = 0; - public static final int REPEAT_MODE_ALL = 1; - public static final int REPEAT_MODE_THIS = 2; - public static final int SAVE_QUEUES = 0; - private static final long MEDIA_SESSION_ACTIONS = - PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE - | PlaybackStateCompat.ACTION_PLAY_PAUSE - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_STOP - | PlaybackStateCompat.ACTION_SEEK_TO; - private final IBinder musicBind = new MusicBinder(); - public int nextPosition = -1; - - public boolean pendingQuit = false; - - @Nullable - public Playback playback; - - private PackageValidator mPackageValidator; - - private final AutoMusicProvider mMusicProvider = get(AutoMusicProvider.class); - - public boolean trackEndedByCrossfade = false; - - public int position = -1; - - private final AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); - - private final AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); - - private final AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); - - private final AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); - - private final AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); - - private final AppWidgetMD3 appWidgetMd3 = AppWidgetMD3.Companion.getInstance(); - - private final AppWidgetCircle appWidgetCircle = AppWidgetCircle.Companion.getInstance(); - - private final BroadcastReceiver widgetIntentReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); - final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); - if (command != null) { - switch (command) { - case AppWidgetClassic.NAME: { - appWidgetClassic.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetSmall.NAME: { - appWidgetSmall.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetBig.NAME: { - appWidgetBig.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetCard.NAME: { - appWidgetCard.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetText.NAME: { - appWidgetText.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetMD3.NAME: { - appWidgetMd3.performUpdate(MusicService.this, ids); - break; - } - case AppWidgetCircle.NAME: { - appWidgetCircle.performUpdate(MusicService.this, ids); - break; - } - } - } - } - }; - private AudioManager audioManager; - private final IntentFilter becomingNoisyReceiverIntentFilter = - new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - private boolean becomingNoisyReceiverRegistered; - private final IntentFilter bluetoothConnectedIntentFilter = - new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); - private boolean bluetoothConnectedRegistered = false; - private final IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - private boolean headsetReceiverRegistered = false; - private MediaSessionCompat mediaSession; - private ContentObserver mediaStoreObserver; - private HandlerThread musicPlayerHandlerThread; - private boolean notHandledMetaChangedForCurrentTrack; - private List originalPlayingQueue = new ArrayList<>(); - private List playingQueue = new ArrayList<>(); - private boolean pausedByTransientLossOfFocus; - - private final BroadcastReceiver becomingNoisyReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, @NonNull Intent intent) { - if (intent.getAction() != null - && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { - pause(); - } - } - }; - - private PlaybackHandler playerHandler; - - private final AudioManager.OnAudioFocusChangeListener audioFocusListener = - new AudioManager.OnAudioFocusChangeListener() { - @Override - public void onAudioFocusChange(final int focusChange) { - playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); - } - }; - - private PlayingNotification playingNotification; - private final BroadcastReceiver updateFavoriteReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - playingNotification.updateFavorite(getCurrentSong(), MusicService.this::startForegroundOrNotify); - startForegroundOrNotify(); - appWidgetCircle.notifyChange(MusicService.this, FAVORITE_STATE_CHANGED); - } - }; - private final BroadcastReceiver lockScreenReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (PreferenceUtil.INSTANCE.isLockScreen() && isPlaying()) { - Intent lockIntent = new Intent(context, LockScreenActivity.class); - lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(lockIntent); - } - } - }; - private QueueSaveHandler queueSaveHandler; - private HandlerThread queueSaveHandlerThread; - private boolean queuesRestored; - private int repeatMode; - private int shuffleMode; - private final SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); - private final BroadcastReceiver bluetoothReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) - && PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { - if (VERSION.SDK_INT >= VERSION_CODES.M) { - if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { - play(); - } - } else { - if (getAudioManager().isBluetoothA2dpOn()) { - play(); - } - } - } - } - } - }; - private final PhoneStateListener phoneStateListener = - new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String incomingNumber) { - switch (state) { - case TelephonyManager.CALL_STATE_IDLE: - // Not in call: Play music - play(); - break; - case TelephonyManager.CALL_STATE_RINGING: - case TelephonyManager.CALL_STATE_OFFHOOK: - // A call is dialing, active or on hold - pause(); - break; - default: - } - super.onCallStateChanged(state, incomingNumber); - } - }; - private final BroadcastReceiver headsetReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (Intent.ACTION_HEADSET_PLUG.equals(action)) { - int state = intent.getIntExtra("state", -1); - switch (state) { - case 0: - pause(); - break; - case 1: - play(); - break; - } - } - } - } - }; - private ThrottledSeekHandler throttledSeekHandler; - private Handler uiThreadHandler; - private PowerManager.WakeLock wakeLock; - private NotificationManager notificationManager; - private boolean isForeground = false; - - private static Bitmap copy(Bitmap bitmap) { - Bitmap.Config config = bitmap.getConfig(); - if (config == null) { - config = Bitmap.Config.RGB_565; - } - try { - return bitmap.copy(config, false); - } catch (OutOfMemoryError e) { - e.printStackTrace(); - return null; - } - } - - private static String getTrackUri(@NonNull Song song) { - return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString(); - } - - @Override - public void onCreate() { - super.onCreate(); - final TelephonyManager telephonyManager = - (TelephonyManager) getSystemService(TELEPHONY_SERVICE); - if (telephonyManager != null) { - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); - } - - final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - if (powerManager != null) { - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); - } - wakeLock.setReferenceCounted(false); - - musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); - musicPlayerHandlerThread.start(); - playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); - - // Set MultiPlayer when crossfade duration is 0 i.e. off - if (PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) { - playback = new MultiPlayer(this); - } else { - playback = new CrossFadePlayer(this); - } - - playback.setCallbacks(this); - - setupMediaSession(); - - // queue saving needs to run on a separate thread so that it doesn't block the playback handler - // events - queueSaveHandlerThread = - new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); - queueSaveHandlerThread.start(); - queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); - - uiThreadHandler = new Handler(); - - registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); - registerReceiver(updateFavoriteReceiver, new IntentFilter(FAVORITE_STATE_CHANGED)); - registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); - - setSessionToken(mediaSession.getSessionToken()); - notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - initNotification(); - - mediaStoreObserver = new MediaStoreObserver(this, playerHandler); - throttledSeekHandler = new ThrottledSeekHandler(this, playerHandler); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); - - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - getContentResolver() - .registerContentObserver( - MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver); - - AudioVolumeObserver audioVolumeObserver = new AudioVolumeObserver(this); - audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this); - - PreferenceUtil.INSTANCE.registerOnSharedPreferenceChangedListener(this); - - restoreState(); - - sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); - - registerHeadsetEvents(); - registerBluetoothConnected(); - - mPackageValidator = new PackageValidator(this, R.xml.allowed_media_browser_callers); - mMusicProvider.setMusicService(this); - } - - @Override - public void onDestroy() { - unregisterReceiver(widgetIntentReceiver); - unregisterReceiver(updateFavoriteReceiver); - unregisterReceiver(lockScreenReceiver); - if (becomingNoisyReceiverRegistered) { - unregisterReceiver(becomingNoisyReceiver); - becomingNoisyReceiverRegistered = false; - } - if (headsetReceiverRegistered) { - unregisterReceiver(headsetReceiver); - headsetReceiverRegistered = false; - } - if (bluetoothConnectedRegistered) { - unregisterReceiver(bluetoothReceiver); - bluetoothConnectedRegistered = false; - } - mediaSession.setActive(false); - quit(); - releaseResources(); - getContentResolver().unregisterContentObserver(mediaStoreObserver); - PreferenceUtil.INSTANCE.unregisterOnSharedPreferenceChangedListener(this); - wakeLock.release(); - - sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED")); - } - - public void acquireWakeLock(long milli) { - wakeLock.acquire(milli); - } - - boolean pausedByZeroVolume; - - @Override - public void onAudioVolumeChanged(int currentVolume, int maxVolume) { - if (PreferenceUtil.INSTANCE.isPauseOnZeroVolume()) { - if (isPlaying() && currentVolume < 1) { - pause(); - System.out.println("Paused"); - pausedByZeroVolume = true; - } else if (pausedByZeroVolume && currentVolume >= 1) { - System.out.println("Played"); - play(); - pausedByZeroVolume = false; - } - } - } - - public void addSong(int position, Song song) { - playingQueue.add(position, song); - originalPlayingQueue.add(position, song); - notifyChange(QUEUE_CHANGED); - } - - public void addSong(Song song) { - playingQueue.add(song); - originalPlayingQueue.add(song); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(int position, List songs) { - playingQueue.addAll(position, songs); - originalPlayingQueue.addAll(position, songs); - notifyChange(QUEUE_CHANGED); - } - - public void addSongs(List songs) { - playingQueue.addAll(songs); - originalPlayingQueue.addAll(songs); - notifyChange(QUEUE_CHANGED); - } - - public void back(boolean force) { - if (getSongProgressMillis() > 2000) { - seek(0); - } else { - playPreviousSong(force); - } - } - - public void clearQueue() { - playingQueue.clear(); - originalPlayingQueue.clear(); - - setPosition(-1); - notifyChange(QUEUE_CHANGED); - } - - public void cycleRepeatMode() { - switch (getRepeatMode()) { - case REPEAT_MODE_NONE: - setRepeatMode(REPEAT_MODE_ALL); - break; - case REPEAT_MODE_ALL: - setRepeatMode(REPEAT_MODE_THIS); - break; - default: - setRepeatMode(REPEAT_MODE_NONE); - break; - } - } - - public int getAudioSessionId() { - if (playback != null) { - return playback.getAudioSessionId(); - } - return -1; - } - - @NonNull - public Song getCurrentSong() { - return getSongAt(getPosition()); - } - - public Song getNextSong() { - if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) { - return null; - } else { - return getSongAt(getNextPosition(false)); - } - } - - @NonNull - public MediaSessionCompat getMediaSession() { - return mediaSession; - } - - public int getNextPosition(boolean force) { - int position = getPosition() + 1; - switch (getRepeatMode()) { - case REPEAT_MODE_ALL: - if (isLastTrack()) { - position = 0; - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (isLastTrack()) { - position = 0; - } - } else { - position -= 1; - } - break; - default: - case REPEAT_MODE_NONE: - if (isLastTrack()) { - position -= 1; - } - break; - } - return position; - } - - @Nullable - public List getPlayingQueue() { - return playingQueue; - } - - public int getPosition() { - return position; - } - - public void setPosition(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(SET_POSITION); - playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); - } - - public int getPreviousPosition(boolean force) { - int newPosition = getPosition() - 1; - switch (repeatMode) { - case REPEAT_MODE_ALL: - if (newPosition < 0) { - if (getPlayingQueue() != null) { - newPosition = getPlayingQueue().size() - 1; - } - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (newPosition < 0) { - if (getPlayingQueue() != null) { - newPosition = getPlayingQueue().size() - 1; - } - } - } else { - newPosition = getPosition(); - } - break; - default: - case REPEAT_MODE_NONE: - if (newPosition < 0) { - newPosition = 0; - } - break; - } - return newPosition; - } - - public long getQueueDurationMillis(int position) { - long duration = 0; - for (int i = position + 1; i < playingQueue.size(); i++) { - duration += playingQueue.get(i).getDuration(); - } - return duration; - } - - public int getRepeatMode() { - return repeatMode; - } - - public void setRepeatMode(final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_NONE: - case REPEAT_MODE_ALL: - case REPEAT_MODE_THIS: - this.repeatMode = repeatMode; - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_REPEAT_MODE, repeatMode) - .apply(); - prepareNext(); - handleAndSendChangeInternal(REPEAT_MODE_CHANGED); - break; - } - } - - public int getShuffleMode() { - return shuffleMode; - } - - public void setShuffleMode(final int shuffleMode) { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_SHUFFLE_MODE, shuffleMode) - .apply(); - switch (shuffleMode) { - case SHUFFLE_MODE_SHUFFLE: - this.shuffleMode = shuffleMode; - if (this.getPlayingQueue() != null) { - ShuffleHelper.INSTANCE.makeShuffleList(this.getPlayingQueue(), getPosition()); - } - position = 0; - break; - case SHUFFLE_MODE_NONE: - this.shuffleMode = shuffleMode; - long currentSongId = Objects.requireNonNull(getCurrentSong()).getId(); - playingQueue = new ArrayList<>(originalPlayingQueue); - int newPosition = 0; - if (getPlayingQueue() != null) { - for (Song song : getPlayingQueue()) { - if (song.getId() == currentSongId) { - newPosition = getPlayingQueue().indexOf(song); - } - } - } - position = newPosition; - break; - } - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); - notifyChange(QUEUE_CHANGED); - } - - @NonNull - public Song getSongAt(int position) { - if (position >= 0 && getPlayingQueue() != null && position < getPlayingQueue().size()) { - return getPlayingQueue().get(position); - } else { - return Song.Companion.getEmptySong(); - } - } - - public int getSongDurationMillis() { - if (playback != null) { - return playback.duration(); - } - return -1; - } - - public int getSongProgressMillis() { - if (playback != null) { - return playback.position(); - } - return -1; - } - - public void handleAndSendChangeInternal(@NonNull final String what) { - handleChangeInternal(what); - sendChangeInternal(what); - } - - public void initNotification() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - && !PreferenceUtil.INSTANCE.isClassicNotification()) { - playingNotification = PlayingNotificationImpl24.Companion.from(this, notificationManager, mediaSession); - } else { - playingNotification = PlayingNotificationClassic.Companion.from(this, notificationManager); - } - } - - public boolean isLastTrack() { - if (getPlayingQueue() != null) { - return getPosition() == getPlayingQueue().size() - 1; - } - return false; - } - - public boolean isPausedByTransientLossOfFocus() { - return pausedByTransientLossOfFocus; - } - - public void setPausedByTransientLossOfFocus(boolean pausedByTransientLossOfFocus) { - this.pausedByTransientLossOfFocus = pausedByTransientLossOfFocus; - } - - public boolean isPlaying() { - return playback != null && playback.isPlaying(); - } - - public void moveSong(int from, int to) { - if (from == to) { - return; - } - final int currentPosition = getPosition(); - Song songToMove = playingQueue.remove(from); - playingQueue.add(to, songToMove); - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - Song tmpSong = originalPlayingQueue.remove(from); - originalPlayingQueue.add(to, tmpSong); - } - if (from > currentPosition && to <= currentPosition) { - position = currentPosition + 1; - } else if (from < currentPosition && to >= currentPosition) { - position = currentPosition - 1; - } else if (from == currentPosition) { - position = to; - } - notifyChange(QUEUE_CHANGED); - } - - public void notifyChange(@NonNull final String what) { - handleAndSendChangeInternal(what); - sendPublicIntent(what); - } - - @NonNull - @Override - public IBinder onBind(Intent intent) { - // For Android auto, need to call super, or onGetRoot won't be called. - if (intent != null && "android.media.browse.MediaBrowserService".equals(intent.getAction())) { - return super.onBind(intent); - } - return musicBind; - } - - @Nullable - @Override - public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, @Nullable Bundle rootHints) { - - - // Check origin to ensure we're not allowing any arbitrary app to browse app contents - if (!mPackageValidator.isKnownCaller(clientPackageName, clientUid)) { - // Request from an untrusted package: return an empty browser root - return new BrowserRoot(AutoMediaIDHelper.MEDIA_ID_EMPTY_ROOT, null); - } else { - /** - * By default return the browsable root. Treat the EXTRA_RECENT flag as a special case - * and return the recent root instead. - */ - boolean isRecentRequest = false; - if (rootHints != null) { - isRecentRequest = rootHints.getBoolean(EXTRA_RECENT); - } - String browserRootPath; - if (isRecentRequest) { - browserRootPath = AutoMediaIDHelper.RECENT_ROOT; - } else { - browserRootPath = AutoMediaIDHelper.MEDIA_ID_ROOT; - } - return new BrowserRoot(browserRootPath, null); - } - } - - @Override - public void onLoadChildren(@NonNull String parentId, @NonNull MediaBrowserServiceCompat.Result> result) { - if (parentId.equals(AutoMediaIDHelper.RECENT_ROOT)) { - Song song = getCurrentSong(); - MediaBrowserCompat.MediaItem mediaItem = new MediaBrowserCompat.MediaItem( - new MediaDescriptionCompat.Builder() - .setMediaId(String.valueOf(song.getId())) - .setTitle(song.getTitle()) - .setSubtitle(song.getArtistName()) - .setIconUri(MusicUtil.getMediaStoreAlbumCoverUri(song.getAlbumId())) - .build(), FLAG_PLAYABLE - ); - result.sendResult(Collections.singletonList(mediaItem)); - } else { - result.sendResult(mMusicProvider.getChildren(parentId, getResources())); - } - } - - @Override - public void onSharedPreferenceChanged( - @NonNull SharedPreferences sharedPreferences, @NonNull String key) { - switch (key) { - case CROSS_FADE_DURATION: - int progress = getSongProgressMillis(); - boolean wasPlaying = isPlaying(); - /* Switch to MultiPlayer if Crossfade duration is 0 and - Playback is not an instance of MultiPlayer */ - if (playback != null) - playback.setCrossFadeDuration(PreferenceUtil.INSTANCE.getCrossFadeDuration()); - if (!(playback instanceof MultiPlayer) && PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) { - if (playback != null) { - playback.release(); - } - playback = null; - playback = new MultiPlayer(this); - playback.setCallbacks(this); - if (openTrackAndPrepareNextAt(position)) { - seek(progress); - if (wasPlaying) { - play(); - } - } - } - /* Switch to CrossFadePlayer if Crossfade duration is greater than 0 and - Playback is not an instance of CrossFadePlayer */ - else if (!(playback instanceof CrossFadePlayer) && PreferenceUtil.INSTANCE.getCrossFadeDuration() > 0) { - if (playback != null) { - playback.release(); - } - playback = null; - playback = new CrossFadePlayer(this); - playback.setCallbacks(this); - if (openTrackAndPrepareNextAt(position)) { - seek(progress); - if (wasPlaying) { - play(); - } - } - } - break; - case ALBUM_ART_ON_LOCK_SCREEN: - case BLURRED_ALBUM_ART: - updateMediaSessionMetaData(); - break; - case COLORED_NOTIFICATION: - updateNotification(); - break; - case CLASSIC_NOTIFICATION: - updateNotification(); - playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify); - playingNotification.setPlaying(isPlaying(),this::startForegroundOrNotify); - break; - case PLAYBACK_SPEED: - updateMediaSessionPlaybackState(); - break; - case TOGGLE_HEADSET: - registerHeadsetEvents(); - break; - } - } - - @Override - public int onStartCommand(@Nullable Intent intent, int flags, int startId) { - if (intent != null && intent.getAction() != null) { - restoreQueuesAndPositionIfNecessary(); - String action = intent.getAction(); - switch (action) { - case ACTION_TOGGLE_PAUSE: - if (isPlaying()) { - pause(); - } else { - play(); - } - break; - case ACTION_PAUSE: - pause(); - break; - case ACTION_PLAY: - play(); - break; - case ACTION_PLAY_PLAYLIST: - playFromPlaylist(intent); - break; - case ACTION_REWIND: - back(true); - break; - case ACTION_SKIP: - playNextSong(true); - break; - case ACTION_STOP: - case ACTION_QUIT: - pendingQuit = false; - quit(); - break; - case ACTION_PENDING_QUIT: - pendingQuit = true; - break; - case TOGGLE_FAVORITE: - MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong()); - break; - } - } - - return START_NOT_STICKY; - } - - @Override - public void onTrackEnded() { - acquireWakeLock(30000); - playerHandler.sendEmptyMessage(TRACK_ENDED); - } - - @Override - public void onTrackEndedWithCrossfade() { - trackEndedByCrossfade = true; - acquireWakeLock(30000); - playerHandler.sendEmptyMessage(TRACK_ENDED); - } - - @Override - public void onTrackWentToNext() { - playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); - } - - @Override - public boolean onUnbind(Intent intent) { - if (!isPlaying()) { - stopSelf(); - } - return true; - } - - public void openQueue( - @Nullable final List playingQueue, - final int startPosition, - final boolean startPlaying) { - if (playingQueue != null - && !playingQueue.isEmpty() - && startPosition >= 0 - && startPosition < playingQueue.size()) { - // it is important to copy the playing queue here first as we might add/remove songs later - originalPlayingQueue = new ArrayList<>(playingQueue); - this.playingQueue = new ArrayList<>(originalPlayingQueue); - - int position = startPosition; - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - ShuffleHelper.INSTANCE.makeShuffleList(this.playingQueue, startPosition); - position = 0; - } - if (startPlaying) { - playSongAt(position); - } else { - setPosition(position); - } - notifyChange(QUEUE_CHANGED); - } - } - - public boolean openTrackAndPrepareNextAt(int position) { - synchronized (this) { - this.position = position; - boolean prepared = openCurrent(); - if (prepared) { - prepareNextImpl(); - } - notifyChange(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; - return prepared; - } - } - - public void pause() { - Log.i(TAG, "Paused"); - pausedByTransientLossOfFocus = false; - if (playback != null && playback.isPlaying()) { - startFadeAnimator(playback, false, () -> { - //Code to run when Animator Ends - if (playback != null) { - playback.pause(); - } - notifyChange(PLAY_STATE_CHANGED); - }); - } - } - - public void forcePause() { - pausedByTransientLossOfFocus = false; - if (playback != null && playback.isPlaying()) { - playback.pause(); - notifyChange(PLAY_STATE_CHANGED); - } - } - - public void play() { - synchronized (this) { - if (requestFocus()) { - if (playback != null && !playback.isPlaying()) { - if (!playback.isInitialized()) { - playSongAt(getPosition()); - } else { - //Don't Start playing when it's casting - if (MusicPlayerRemote.INSTANCE.isCasting()) { - return; - } - startFadeAnimator(playback, true, () -> { - // Code when Animator Ends - if (!becomingNoisyReceiverRegistered) { - registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); - becomingNoisyReceiverRegistered = true; - } - if (notHandledMetaChangedForCurrentTrack) { - handleChangeInternal(META_CHANGED); - notHandledMetaChangedForCurrentTrack = false; - } - - // fixes a bug where the volume would stay ducked because the - // AudioManager.AUDIOFOCUS_GAIN event is not sent - playerHandler.removeMessages(DUCK); - playerHandler.sendEmptyMessage(UNDUCK); - }); - //Start Playback with Animator - playback.start(); - notifyChange(PLAY_STATE_CHANGED); - } - } - } else { - Toast.makeText( - this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT) - .show(); - } - } - } - - public void playNextSong(boolean force) { - playSongAt(getNextPosition(force)); - } - - public void playPreviousSong(boolean force) { - playSongAt(getPreviousPosition(force)); - } - - public void playSongAt(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(PLAY_SONG); - playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); - } - - public void playSongAtImpl(int position) { - if (!trackEndedByCrossfade) { - // This is only imp if we are using crossfade - if (playback instanceof CrossFadePlayer) { - ((CrossFadePlayer) playback).sourceChangedByUser(); - } - } else { - trackEndedByCrossfade = false; - } - if (openTrackAndPrepareNextAt(position)) { - play(); - } else { - Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT) - .show(); - } - } - - public void playSongs(ArrayList songs, int shuffleMode) { - if (songs != null && !songs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - int startPosition = new Random().nextInt(songs.size()); - openQueue(songs, startPosition, false); - setShuffleMode(shuffleMode); - } else { - openQueue(songs, 0, false); - } - play(); - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); - } - } - - public void prepareNextImpl() { - synchronized (this) { - try { - int nextPosition = getNextPosition(false); - if (playback != null) { - playback.setNextDataSource(getTrackUri(Objects.requireNonNull(getSongAt(nextPosition)))); - } - this.nextPosition = nextPosition; - } catch (Exception ignored) { - } - } - } - - public void quit() { - pause(); - stopForeground(true); - notificationManager.cancel(PlayingNotification.NOTIFICATION_ID); - - closeAudioEffectSession(); - getAudioManager().abandonAudioFocus(audioFocusListener); - stopSelf(); - } - - public void releaseWakeLock() { - if (wakeLock.isHeld()) { - wakeLock.release(); - } - } - - public void removeSong(int position) { - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - playingQueue.remove(position); - originalPlayingQueue.remove(position); - } else { - originalPlayingQueue.remove(playingQueue.remove(position)); - } - - rePosition(position); - - notifyChange(QUEUE_CHANGED); - } - - public void removeSongImpl(@NonNull Song song) { - for (int i = 0; i < playingQueue.size(); i++) { - if (playingQueue.get(i).getId() == song.getId()) { - playingQueue.remove(i); - rePosition(i); - } - } - for (int i = 0; i < originalPlayingQueue.size(); i++) { - if (originalPlayingQueue.get(i).getId() == song.getId()) { - originalPlayingQueue.remove(i); - } - } - } - - public void removeSong(@NonNull Song song) { - removeSongImpl(song); - notifyChange(QUEUE_CHANGED); - } - - public void removeSongs(@NonNull List songs) { - for (Song song : songs) { - removeSongImpl(song); - } - notifyChange(QUEUE_CHANGED); - } - - public synchronized void restoreQueuesAndPositionIfNecessary() { - if (!queuesRestored && playingQueue.isEmpty()) { - List restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue(); - List restoredOriginalQueue = - MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue(); - int restoredPosition = - PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); - int restoredPositionInTrack = - PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); - - if (restoredQueue.size() > 0 - && restoredQueue.size() == restoredOriginalQueue.size() - && restoredPosition != -1) { - this.originalPlayingQueue = restoredOriginalQueue; - this.playingQueue = restoredQueue; - - position = restoredPosition; - openCurrent(); - prepareNext(); - - if (restoredPositionInTrack > 0) { - seek(restoredPositionInTrack); - } - - notHandledMetaChangedForCurrentTrack = true; - sendChangeInternal(META_CHANGED); - sendChangeInternal(QUEUE_CHANGED); - } - } - queuesRestored = true; - } - - public void runOnUiThread(Runnable runnable) { - uiThreadHandler.post(runnable); - } - - public void savePositionInTrack() { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()) - .apply(); - } - - public void saveQueuesImpl() { - MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); - } - - public void saveState() { - saveQueues(); - savePosition(); - savePositionInTrack(); - } - - public int seek(int millis) { - synchronized (this) { - try { - int newPosition = 0; - if (playback != null) { - newPosition = playback.seek(millis); - } - throttledSeekHandler.notifySeek(); - return newPosition; - } catch (Exception e) { - return -1; - } - } - } - - // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch - public void sendPublicIntent(@NonNull final String what) { - final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); - - final Song song = getCurrentSong(); - - if (song != null) { - intent.putExtra("id", song.getId()); - intent.putExtra("artist", song.getArtistName()); - intent.putExtra("album", song.getAlbumName()); - intent.putExtra("track", song.getTitle()); - intent.putExtra("duration", song.getDuration()); - intent.putExtra("position", (long) getSongProgressMillis()); - intent.putExtra("playing", isPlaying()); - intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); - sendStickyBroadcast(intent); - } - } - - public void toggleShuffle() { - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - setShuffleMode(SHUFFLE_MODE_SHUFFLE); - } else { - setShuffleMode(SHUFFLE_MODE_NONE); - } - } - - public void updateMediaSessionPlaybackState() { - PlaybackStateCompat.Builder stateBuilder = - new PlaybackStateCompat.Builder() - .setActions(MEDIA_SESSION_ACTIONS) - .setState( - isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, - getSongProgressMillis(), - PreferenceUtil.INSTANCE.getPlaybackSpeed()); - - setCustomAction(stateBuilder); - - mediaSession.setPlaybackState(stateBuilder.build()); - } - - public void updateNotification() { - if (playingNotification != null && getCurrentSong().getId() != -1) { - stopForegroundAndNotification(); - initNotification(); - } - } - - public void updateMediaSessionMetaData() { - Log.i(TAG, "onResourceReady: "); - final Song song = getCurrentSong(); - - if (song.getId() == -1) { - mediaSession.setMetadata(null); - return; - } - - final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.getArtistName()) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.getArtistName()) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.getAlbumName()) - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.getTitle()) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.getDuration()) - .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) - .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.getYear()) - .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); - - metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); - - if (PreferenceUtil.INSTANCE.isAlbumArtOnLockScreen()) { - final Point screenSize = RetroUtil.getScreenSize(MusicService.this); - final RequestBuilder request = GlideApp.with(MusicService.this).asBitmap().songCoverOptions(song).load(RetroGlideExtension.INSTANCE.getSongModel(song)); - if (PreferenceUtil.INSTANCE.isBlurredAlbumArt()) { - request.transform(new BlurTransformation.Builder(MusicService.this).build()); - } - runOnUiThread(new Runnable() { - @Override - public void run() { - request.into(new SimpleTarget(screenSize.x, screenSize.y) { - @Override - public void onLoadFailed(Drawable errorDrawable) { - super.onLoadFailed(errorDrawable); - mediaSession.setMetadata(metaData.build()); - } - - @Override - public void onResourceReady(@NonNull Bitmap resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { - metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); - mediaSession.setMetadata(metaData.build()); - } - }); - } - }); - } else { - mediaSession.setMetadata(metaData.build()); - } - } - - private void closeAudioEffectSession() { - final Intent audioEffectsIntent = - new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); - if (playback != null) { - audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); - } - audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); - sendBroadcast(audioEffectsIntent); - } - - private AudioManager getAudioManager() { - if (audioManager == null) { - audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - } - return audioManager; - } - - private void handleChangeInternal(@NonNull final String what) { - switch (what) { - case PLAY_STATE_CHANGED: - updateMediaSessionPlaybackState(); - final boolean isPlaying = isPlaying(); - if (!isPlaying && getSongProgressMillis() > 0) { - savePositionInTrack(); - } - songPlayCountHelper.notifyPlayStateChanged(isPlaying); - playingNotification.setPlaying(isPlaying, this::startForegroundOrNotify); - startForegroundOrNotify(); - break; - case FAVORITE_STATE_CHANGED: - playingNotification.updateFavorite(getCurrentSong(), this::startForegroundOrNotify); - case META_CHANGED: - playingNotification.updateMetadata(getCurrentSong(), this::startForegroundOrNotify); - updateMediaSessionMetaData(); - updateMediaSessionPlaybackState(); - savePosition(); - savePositionInTrack(); - final Song currentSong = getCurrentSong(); - HistoryStore.getInstance(this).addSongId(currentSong.getId()); - if (songPlayCountHelper.shouldBumpPlayCount()) { - SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().getId()); - } - songPlayCountHelper.notifySongChanged(currentSong); - break; - case QUEUE_CHANGED: - updateMediaSessionMetaData(); // because playing queue size might have changed - saveState(); - if (playingQueue.size() > 0) { - prepareNext(); - } else { - stopForegroundAndNotification(); - } - break; - } - } - - private Unit startForegroundOrNotify() { - if (playingNotification != null && getCurrentSong().getId() != -1) { - boolean isPlaying = isPlaying(); - - if ((isForeground != isPlaying) && !isPlaying) { - // This makes the notification dismissible - // We can't call stopForeground(false) on A12 though, which may result in crashes - // when we call startForeground after that e.g. when Alarm goes off, - if (Build.VERSION.SDK_INT < VERSION_CODES.S) { - stopForeground(false); - isForeground = false; - } - } - - if (!isForeground && isPlaying) { - // Specify that this is a media service, if supported. - if (VersionUtils.hasQ()) { - startForeground( - PlayingNotification.NOTIFICATION_ID, playingNotification.build(), - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK - ); - } else { - startForeground(PlayingNotification.NOTIFICATION_ID, playingNotification.build()); - } - - isForeground = true; - } else { - // If we are already in foreground just update the notification - notificationManager.notify( - PlayingNotification.NOTIFICATION_ID, playingNotification.build() - ); - } - } - return Unit.INSTANCE; - } - - private void stopForegroundAndNotification() { - stopForeground(true); - notificationManager.cancel(PlayingNotification.NOTIFICATION_ID); - - isForeground = false; - } - - private boolean openCurrent() { - synchronized (this) { - try { - if (playback != null) { - return playback.setDataSource(getTrackUri(Objects.requireNonNull(getCurrentSong()))); - } - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - return false; - } - - private void playFromPlaylist(Intent intent) { - AbsSmartPlaylist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); - int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); - if (playlist != null) { - List playlistSongs = playlist.songs(); - if (!playlistSongs.isEmpty()) { - if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { - int startPosition = new Random().nextInt(playlistSongs.size()); - openQueue(playlistSongs, startPosition, true); - setShuffleMode(shuffleMode); - } else { - openQueue(playlistSongs, 0, true); - } - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG) - .show(); - } - } else { - Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); - } - } - - private void prepareNext() { - playerHandler.removeMessages(PREPARE_NEXT); - playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); - } - - private void rePosition(int deletedPosition) { - int currentPosition = getPosition(); - if (deletedPosition < currentPosition) { - position = currentPosition - 1; - } else if (deletedPosition == currentPosition) { - if (playingQueue.size() > deletedPosition) { - setPosition(position); - } else { - setPosition(position - 1); - } - } - } - - private void registerBluetoothConnected() { - Log.i(TAG, "registerBluetoothConnected: "); - if (!bluetoothConnectedRegistered) { - registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter); - bluetoothConnectedRegistered = true; - } - } - - private void registerHeadsetEvents() { - if (!headsetReceiverRegistered && PreferenceUtil.INSTANCE.isHeadsetPlugged()) { - registerReceiver(headsetReceiver, headsetReceiverIntentFilter); - headsetReceiverRegistered = true; - } - } - - private void releaseResources() { - playerHandler.removeCallbacksAndMessages(null); - musicPlayerHandlerThread.quitSafely(); - queueSaveHandler.removeCallbacksAndMessages(null); - queueSaveHandlerThread.quitSafely(); - if (playback != null) { - playback.release(); - } - playback = null; - mediaSession.release(); - } - - private boolean requestFocus() { - return (getAudioManager() - .requestAudioFocus( - audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) - == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); - } - - private void restoreState() { - shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); - repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); - handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); - handleAndSendChangeInternal(REPEAT_MODE_CHANGED); - - playerHandler.removeMessages(RESTORE_QUEUES); - playerHandler.sendEmptyMessage(RESTORE_QUEUES); - } - - private void savePosition() { - PreferenceManager.getDefaultSharedPreferences(this) - .edit() - .putInt(SAVED_POSITION, getPosition()) - .apply(); - } - - private void saveQueues() { - queueSaveHandler.removeMessages(SAVE_QUEUES); - queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); - } - - private void sendChangeInternal(final String what) { - sendBroadcast(new Intent(what)); - appWidgetBig.notifyChange(this, what); - appWidgetClassic.notifyChange(this, what); - appWidgetSmall.notifyChange(this, what); - appWidgetCard.notifyChange(this, what); - appWidgetText.notifyChange(this, what); - appWidgetMd3.notifyChange(this, what); - appWidgetCircle.notifyChange(this, what); - } - - private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) { - int repeatIcon = R.drawable.ic_repeat; // REPEAT_MODE_NONE - if (getRepeatMode() == REPEAT_MODE_THIS) { - repeatIcon = R.drawable.ic_repeat_one; - } else if (getRepeatMode() == REPEAT_MODE_ALL) { - repeatIcon = R.drawable.ic_repeat_white_circle; - } - stateBuilder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon) - .build()); - - final int shuffleIcon = - getShuffleMode() == SHUFFLE_MODE_NONE - ? R.drawable.ic_shuffle_off_circled - : R.drawable.ic_shuffle_on_circled; - stateBuilder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon) - .build()); - - final int favoriteIcon = - MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong()) - ? R.drawable.ic_favorite - : R.drawable.ic_favorite_border; - stateBuilder.addCustomAction( - new PlaybackStateCompat.CustomAction.Builder( - TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon) - .build()); - } - - private void setupMediaSession() { - ComponentName mediaButtonReceiverComponentName = - new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); - - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); - - PendingIntent mediaButtonReceiverPendingIntent; - mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, - VersionUtils.INSTANCE.hasMarshmallow() ? PendingIntent.FLAG_IMMUTABLE : 0); - - mediaSession = new MediaSessionCompat( - this, - "RetroMusicPlayer", - mediaButtonReceiverComponentName, - mediaButtonReceiverPendingIntent); - MediaSessionCallback mediasessionCallback = - new MediaSessionCallback(getApplicationContext(), this); - mediaSession.setCallback(mediasessionCallback); - mediaSession.setActive(true); - mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); - } - - public class MusicBinder extends Binder { - - @NonNull - public MusicService getService() { - return MusicService.this; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt new file mode 100644 index 000000000..03336da2a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt @@ -0,0 +1,1539 @@ +/* + * Copyright (c) 2019 Hemanth Savarala. + * + * Licensed under the GNU General Public License v3 + * + * This is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by + * the Free Software Foundation either version 3 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + */ +package code.name.monkey.retromusic.service + +import android.app.NotificationManager +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.bluetooth.BluetoothDevice +import android.content.* +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.content.pm.ServiceInfo +import android.database.ContentObserver +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.media.AudioManager +import android.media.AudioManager.OnAudioFocusChangeListener +import android.media.audiofx.AudioEffect +import android.os.* +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.os.PowerManager.WakeLock +import android.provider.MediaStore +import android.support.v4.media.MediaBrowserCompat +import android.support.v4.media.MediaDescriptionCompat +import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat +import android.telephony.PhoneStateListener +import android.telephony.TelephonyManager +import android.util.Log +import android.widget.Toast +import androidx.core.content.getSystemService +import androidx.media.AudioAttributesCompat +import androidx.media.AudioAttributesCompat.CONTENT_TYPE_MUSIC +import androidx.media.AudioFocusRequestCompat +import androidx.media.AudioManagerCompat +import androidx.media.MediaBrowserServiceCompat +import androidx.preference.PreferenceManager +import code.name.monkey.appthemehelper.util.VersionUtils.hasMarshmallow +import code.name.monkey.appthemehelper.util.VersionUtils.hasQ +import code.name.monkey.retromusic.* +import code.name.monkey.retromusic.activities.LockScreenActivity +import code.name.monkey.retromusic.appwidgets.* +import code.name.monkey.retromusic.auto.AutoMediaIDHelper +import code.name.monkey.retromusic.auto.AutoMusicProvider +import code.name.monkey.retromusic.glide.BlurTransformation +import code.name.monkey.retromusic.glide.GlideApp +import code.name.monkey.retromusic.glide.RetroGlideExtension.getSongModel +import code.name.monkey.retromusic.helper.MusicPlayerRemote.isCasting +import code.name.monkey.retromusic.helper.ShuffleHelper.makeShuffleList +import code.name.monkey.retromusic.model.Song +import code.name.monkey.retromusic.model.Song.Companion.emptySong +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist +import code.name.monkey.retromusic.providers.HistoryStore +import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore +import code.name.monkey.retromusic.providers.SongPlayCountStore +import code.name.monkey.retromusic.service.AudioFader.Companion.startFadeAnimator +import code.name.monkey.retromusic.service.notification.PlayingNotification +import code.name.monkey.retromusic.service.notification.PlayingNotificationClassic.Companion.from +import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl24.Companion.from +import code.name.monkey.retromusic.service.playback.Playback +import code.name.monkey.retromusic.service.playback.Playback.PlaybackCallbacks +import code.name.monkey.retromusic.util.MusicUtil.getMediaStoreAlbumCoverUri +import code.name.monkey.retromusic.util.MusicUtil.getSongFileUri +import code.name.monkey.retromusic.util.MusicUtil.isFavorite +import code.name.monkey.retromusic.util.MusicUtil.toggleFavorite +import code.name.monkey.retromusic.util.PackageValidator +import code.name.monkey.retromusic.util.PreferenceUtil.crossFadeDuration +import code.name.monkey.retromusic.util.PreferenceUtil.isAlbumArtOnLockScreen +import code.name.monkey.retromusic.util.PreferenceUtil.isBluetoothSpeaker +import code.name.monkey.retromusic.util.PreferenceUtil.isBlurredAlbumArt +import code.name.monkey.retromusic.util.PreferenceUtil.isClassicNotification +import code.name.monkey.retromusic.util.PreferenceUtil.isHeadsetPlugged +import code.name.monkey.retromusic.util.PreferenceUtil.isLockScreen +import code.name.monkey.retromusic.util.PreferenceUtil.isPauseOnZeroVolume +import code.name.monkey.retromusic.util.PreferenceUtil.playbackSpeed +import code.name.monkey.retromusic.util.PreferenceUtil.registerOnSharedPreferenceChangedListener +import code.name.monkey.retromusic.util.PreferenceUtil.unregisterOnSharedPreferenceChangedListener +import code.name.monkey.retromusic.util.RetroUtil +import code.name.monkey.retromusic.volume.AudioVolumeObserver +import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.request.target.SimpleTarget +import com.bumptech.glide.request.transition.Transition +import org.koin.java.KoinJavaComponent.get +import java.util.* + +/** + * @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More + */ +class MusicService : MediaBrowserServiceCompat(), + OnSharedPreferenceChangeListener, PlaybackCallbacks, OnAudioVolumeChangedListener { + private val musicBind: IBinder = MusicBinder() + + @JvmField + var nextPosition = -1 + + @JvmField + var pendingQuit = false + + @JvmField + var playback: Playback? = null + private var mPackageValidator: PackageValidator? = null + private val mMusicProvider = get(AutoMusicProvider::class.java) + private var trackEndedByCrossfade = false + + @JvmField + var position = -1 + private val appWidgetBig = AppWidgetBig.instance + private val appWidgetCard = AppWidgetCard.instance + private val appWidgetClassic = AppWidgetClassic.instance + private val appWidgetSmall = AppWidgetSmall.instance + private val appWidgetText = AppWidgetText.instance + private val appWidgetMd3 = AppWidgetMD3.instance + private val widgetIntentReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME) + val ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS) + if (command != null) { + when (command) { + AppWidgetClassic.NAME -> { + appWidgetClassic.performUpdate(this@MusicService, ids) + } + AppWidgetSmall.NAME -> { + appWidgetSmall.performUpdate(this@MusicService, ids) + } + AppWidgetBig.NAME -> { + appWidgetBig.performUpdate(this@MusicService, ids) + } + AppWidgetCard.NAME -> { + appWidgetCard.performUpdate(this@MusicService, ids) + } + AppWidgetText.NAME -> { + appWidgetText.performUpdate(this@MusicService, ids) + } + AppWidgetMD3.NAME -> { + appWidgetMd3.performUpdate(this@MusicService, ids) + } + } + } + } + } + private var audioManager: AudioManager? = null + get() { + if (field == null) { + field = getSystemService() + } + return field + } + + private val becomingNoisyReceiverIntentFilter = + IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY) + private var becomingNoisyReceiverRegistered = false + private val bluetoothConnectedIntentFilter = IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED) + private var bluetoothConnectedRegistered = false + private val headsetReceiverIntentFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG) + private var headsetReceiverRegistered = false + private var mediaSession: MediaSessionCompat? = null + private lateinit var mediaStoreObserver: ContentObserver + private var musicPlayerHandlerThread: HandlerThread? = null + private var notHandledMetaChangedForCurrentTrack = false + private var originalPlayingQueue = mutableListOf() + + @JvmField + var playingQueue = mutableListOf() + var isPausedByTransientLossOfFocus = false + private val becomingNoisyReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != null + && intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY + ) { + pause() + } + } + } + private var playerHandler: PlaybackHandler? = null + private val audioFocusListener: OnAudioFocusChangeListener = + OnAudioFocusChangeListener { focusChange -> + playerHandler?.obtainMessage(FOCUS_CHANGE, focusChange, 0)?.sendToTarget() + } + private var playingNotification: PlayingNotification? = null + private val updateFavoriteReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + playingNotification!!.updateFavorite(currentSong) { startForegroundOrNotify() } + startForegroundOrNotify() + } + } + private val lockScreenReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (isLockScreen && isPlaying) { + val lockIntent = Intent(context, LockScreenActivity::class.java) + lockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(lockIntent) + } + } + } + private var queueSaveHandler: QueueSaveHandler? = null + private var queueSaveHandlerThread: HandlerThread? = null + private var queuesRestored = false + + @JvmField + var repeatMode = 0 + + @JvmField + var shuffleMode = 0 + private val songPlayCountHelper = SongPlayCountHelper() + + private val bluetoothReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + if (action != null) { + if (BluetoothDevice.ACTION_ACL_CONNECTED == action && isBluetoothSpeaker) { + if (audioManager!!.isBluetoothA2dpOn) { + play() + } + } + } + } + } + + private val phoneStateListener: PhoneStateListener = object : PhoneStateListener() { + override fun onCallStateChanged(state: Int, incomingNumber: String) { + when (state) { + TelephonyManager.CALL_STATE_IDLE -> // Not in call: Play music + play() + TelephonyManager.CALL_STATE_RINGING, TelephonyManager.CALL_STATE_OFFHOOK -> // A call is dialing, active or on hold + pause() + else -> {} + } + super.onCallStateChanged(state, incomingNumber) + } + } + + private val headsetReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + if (action != null) { + if (Intent.ACTION_HEADSET_PLUG == action) { + when (intent.getIntExtra("state", -1)) { + 0 -> pause() + 1 -> play() + } + } + } + } + } + private var throttledSeekHandler: ThrottledSeekHandler? = null + private var uiThreadHandler: Handler? = null + private var wakeLock: WakeLock? = null + private var notificationManager: NotificationManager? = null + private var isForeground = false + override fun onCreate() { + super.onCreate() + val telephonyManager = getSystemService() + telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) + val powerManager = getSystemService() + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name) + } + wakeLock?.setReferenceCounted(false) + musicPlayerHandlerThread = HandlerThread("PlaybackHandler") + musicPlayerHandlerThread?.start() + playerHandler = PlaybackHandler(this, musicPlayerHandlerThread!!.looper) + + // Set MultiPlayer when crossfade duration is 0 i.e. off + playback = if (crossFadeDuration == 0) { + MultiPlayer(this) + } else { + CrossFadePlayer(this) + } + playback?.setCallbacks(this) + setupMediaSession() + + // queue saving needs to run on a separate thread so that it doesn't block the playback handler + // events + queueSaveHandlerThread = + HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND) + queueSaveHandlerThread?.start() + queueSaveHandler = QueueSaveHandler(this, queueSaveHandlerThread!!.looper) + uiThreadHandler = Handler() + registerReceiver(widgetIntentReceiver, IntentFilter(APP_WIDGET_UPDATE)) + registerReceiver(updateFavoriteReceiver, IntentFilter(FAVORITE_STATE_CHANGED)) + registerReceiver(lockScreenReceiver, IntentFilter(Intent.ACTION_SCREEN_OFF)) + sessionToken = mediaSession?.sessionToken + notificationManager = getSystemService() + initNotification() + mediaStoreObserver = MediaStoreObserver(this, playerHandler!!) + throttledSeekHandler = ThrottledSeekHandler(this, playerHandler!!) + contentResolver + .registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Albums.INTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Artists.INTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Genres.INTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + contentResolver + .registerContentObserver( + MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI, true, mediaStoreObserver + ) + val audioVolumeObserver = AudioVolumeObserver(this) + audioVolumeObserver.register(AudioManager.STREAM_MUSIC, this) + registerOnSharedPreferenceChangedListener(this) + restoreState() + sendBroadcast(Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")) + registerHeadsetEvents() + registerBluetoothConnected() + mPackageValidator = PackageValidator(this, R.xml.allowed_media_browser_callers) + mMusicProvider.setMusicService(this) + } + + override fun onDestroy() { + unregisterReceiver(widgetIntentReceiver) + unregisterReceiver(updateFavoriteReceiver) + unregisterReceiver(lockScreenReceiver) + if (becomingNoisyReceiverRegistered) { + unregisterReceiver(becomingNoisyReceiver) + becomingNoisyReceiverRegistered = false + } + if (headsetReceiverRegistered) { + unregisterReceiver(headsetReceiver) + headsetReceiverRegistered = false + } + if (bluetoothConnectedRegistered) { + unregisterReceiver(bluetoothReceiver) + bluetoothConnectedRegistered = false + } + mediaSession!!.isActive = false + quit() + releaseResources() + contentResolver.unregisterContentObserver(mediaStoreObserver) + unregisterOnSharedPreferenceChangedListener(this) + wakeLock!!.release() + sendBroadcast(Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_DESTROYED")) + } + + private fun acquireWakeLock(milli: Long) { + wakeLock?.acquire(milli) + } + + var pausedByZeroVolume = false + override fun onAudioVolumeChanged(currentVolume: Int, maxVolume: Int) { + if (isPauseOnZeroVolume) { + if (isPlaying && currentVolume < 1) { + pause() + println("Paused") + pausedByZeroVolume = true + } else if (pausedByZeroVolume && currentVolume >= 1) { + println("Played") + play() + pausedByZeroVolume = false + } + } + } + + fun addSong(position: Int, song: Song) { + playingQueue.add(position, song) + originalPlayingQueue.add(position, song) + notifyChange(QUEUE_CHANGED) + } + + fun addSong(song: Song) { + playingQueue.add(song) + originalPlayingQueue.add(song) + notifyChange(QUEUE_CHANGED) + } + + fun addSongs(position: Int, songs: List?) { + playingQueue.addAll(position, songs!!) + originalPlayingQueue.addAll(position, songs) + notifyChange(QUEUE_CHANGED) + } + + fun addSongs(songs: List?) { + playingQueue.addAll(songs!!) + originalPlayingQueue.addAll(songs) + notifyChange(QUEUE_CHANGED) + } + + fun back(force: Boolean) { + if (songProgressMillis > 2000) { + seek(0) + } else { + playPreviousSong(force) + } + } + + fun clearQueue() { + playingQueue.clear() + originalPlayingQueue.clear() + setPosition(-1) + notifyChange(QUEUE_CHANGED) + } + + fun cycleRepeatMode() { + when (getRepeatMode()) { + REPEAT_MODE_NONE -> setRepeatMode(REPEAT_MODE_ALL) + REPEAT_MODE_ALL -> setRepeatMode(REPEAT_MODE_THIS) + else -> setRepeatMode(REPEAT_MODE_NONE) + } + } + + val audioSessionId: Int + get() = if (playback != null) { + playback!!.audioSessionId + } else -1 + val currentSong: Song + get() = getSongAt(getPosition()) + val nextSong: Song? + get() = if (isLastTrack && repeatMode == REPEAT_MODE_NONE) { + null + } else { + getSongAt(getNextPosition(false)) + } + + private fun getNextPosition(force: Boolean): Int { + var position = getPosition() + 1 + when (getRepeatMode()) { + REPEAT_MODE_ALL -> if (isLastTrack) { + position = 0 + } + REPEAT_MODE_THIS -> if (force) { + if (isLastTrack) { + position = 0 + } + } else { + position -= 1 + } + REPEAT_MODE_NONE -> if (isLastTrack) { + position -= 1 + } + else -> if (isLastTrack) { + position -= 1 + } + } + return position + } + + private fun getPlayingQueue(): List { + return playingQueue + } + + private fun getPosition(): Int { + return position + } + + private fun setPosition(position: Int) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler?.removeMessages(SET_POSITION) + playerHandler?.obtainMessage(SET_POSITION, position, 0)?.sendToTarget() + } + + private fun getPreviousPosition(force: Boolean): Int { + var newPosition = getPosition() - 1 + when (repeatMode) { + REPEAT_MODE_ALL -> if (newPosition < 0) { + newPosition = getPlayingQueue().size - 1 + } + REPEAT_MODE_THIS -> if (force) { + if (newPosition < 0) { + newPosition = getPlayingQueue().size - 1 + } + } else { + newPosition = getPosition() + } + REPEAT_MODE_NONE -> if (newPosition < 0) { + newPosition = 0 + } + else -> if (newPosition < 0) { + newPosition = 0 + } + } + return newPosition + } + + fun getQueueDurationMillis(position: Int): Long { + var duration: Long = 0 + for (i in position + 1 until playingQueue.size) { + duration += playingQueue[i].duration + } + return duration + } + + fun getRepeatMode(): Int { + return repeatMode + } + + fun setRepeatMode(repeatMode: Int) { + when (repeatMode) { + REPEAT_MODE_NONE, REPEAT_MODE_ALL, REPEAT_MODE_THIS -> { + this.repeatMode = repeatMode + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_REPEAT_MODE, repeatMode) + .apply() + prepareNext() + handleAndSendChangeInternal(REPEAT_MODE_CHANGED) + } + } + } + + private fun getShuffleMode(): Int { + return shuffleMode + } + + private fun setShuffleMode(shuffleMode: Int) { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_SHUFFLE_MODE, shuffleMode) + .apply() + when (shuffleMode) { + SHUFFLE_MODE_SHUFFLE -> { + this.shuffleMode = shuffleMode + makeShuffleList(getPlayingQueue().toMutableList(), getPosition()) + position = 0 + } + SHUFFLE_MODE_NONE -> { + this.shuffleMode = shuffleMode + val currentSongId = Objects.requireNonNull(currentSong).id + playingQueue = ArrayList(originalPlayingQueue) + var newPosition = 0 + for (song in getPlayingQueue()) { + if (song.id == currentSongId) { + newPosition = getPlayingQueue().indexOf(song) + } + } + position = newPosition + } + } + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED) + notifyChange(QUEUE_CHANGED) + } + + private fun getSongAt(position: Int): Song { + return if ((position >= 0) && (position < getPlayingQueue().size)) { + getPlayingQueue()[position] + } else { + emptySong + } + } + + val songDurationMillis: Int + get() = if (playback != null) { + playback!!.duration() + } else -1 + val songProgressMillis: Int + get() = if (playback != null) { + playback!!.position() + } else -1 + + fun handleAndSendChangeInternal(what: String) { + handleChangeInternal(what) + sendChangeInternal(what) + } + + private fun initNotification() { + playingNotification = if (VERSION.SDK_INT >= VERSION_CODES.N + && !isClassicNotification + ) { + from(this, notificationManager!!, mediaSession!!) + } else { + from(this, notificationManager!!) + } + } + + val isLastTrack: Boolean + get() = getPosition() == playingQueue.size - 1 + val isPlaying: Boolean + get() = playback != null && playback!!.isPlaying + + fun moveSong(from: Int, to: Int) { + if (from == to) { + return + } + val currentPosition = getPosition() + val songToMove = playingQueue.removeAt(from) + playingQueue.add(to, songToMove) + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + val tmpSong = originalPlayingQueue.removeAt(from) + originalPlayingQueue.add(to, tmpSong) + } + when { + currentPosition in to until from -> { + position = currentPosition + 1 + } + currentPosition in (from + 1)..to -> { + position = currentPosition - 1 + } + from == currentPosition -> { + position = to + } + } + notifyChange(QUEUE_CHANGED) + } + + fun notifyChange(what: String) { + handleAndSendChangeInternal(what) + sendPublicIntent(what) + } + + override fun onBind(intent: Intent): IBinder { + // For Android auto, need to call super, or onGetRoot won't be called. + return if ("android.media.browse.MediaBrowserService" == intent.action) { + super.onBind(intent)!! + } else musicBind + } + + override fun onGetRoot( + clientPackageName: String, + clientUid: Int, + rootHints: Bundle? + ): BrowserRoot { + + + // Check origin to ensure we're not allowing any arbitrary app to browse app contents + return if (!mPackageValidator!!.isKnownCaller(clientPackageName, clientUid)) { + // Request from an untrusted package: return an empty browser root + BrowserRoot(AutoMediaIDHelper.MEDIA_ID_EMPTY_ROOT, null) + } else { + /** + * By default return the browsable root. Treat the EXTRA_RECENT flag as a special case + * and return the recent root instead. + */ + var isRecentRequest = false + if (rootHints != null) { + isRecentRequest = + rootHints.getBoolean(BrowserRoot.EXTRA_RECENT) + } + val browserRootPath = if (isRecentRequest) { + AutoMediaIDHelper.RECENT_ROOT + } else { + AutoMediaIDHelper.MEDIA_ID_ROOT + } + BrowserRoot(browserRootPath, null) + } + } + + override fun onLoadChildren( + parentId: String, + result: Result> + ) { + if (parentId == AutoMediaIDHelper.RECENT_ROOT) { + val song = currentSong + val mediaItem = MediaBrowserCompat.MediaItem( + MediaDescriptionCompat.Builder() + .setMediaId(song.id.toString()) + .setTitle(song.title) + .setSubtitle(song.artistName) + .setIconUri(getMediaStoreAlbumCoverUri(song.albumId)) + .build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE + ) + result.sendResult(listOf(mediaItem)) + } else { + result.sendResult(mMusicProvider.getChildren(parentId, resources)) + } + } + + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences, key: String + ) { + when (key) { + CROSS_FADE_DURATION -> { + val progress = songProgressMillis + val wasPlaying = isPlaying + /* Switch to MultiPlayer if Crossfade duration is 0 and + Playback is not an instance of MultiPlayer */if (playback != null) playback?.setCrossFadeDuration( + crossFadeDuration + ) + if (playback !is MultiPlayer && crossFadeDuration == 0) { + if (playback != null) { + playback?.release() + } + playback = null + playback = MultiPlayer(this) + playback?.setCallbacks(this) + if (openTrackAndPrepareNextAt(position)) { + seek(progress) + if (wasPlaying) { + play() + } + } + } else if (playback !is CrossFadePlayer && crossFadeDuration > 0) { + if (playback != null) { + playback?.release() + } + playback = null + playback = CrossFadePlayer(this) + playback?.setCallbacks(this) + if (openTrackAndPrepareNextAt(position)) { + seek(progress) + if (wasPlaying) { + play() + } + } + } + } + ALBUM_ART_ON_LOCK_SCREEN, BLURRED_ALBUM_ART -> updateMediaSessionMetaData() + COLORED_NOTIFICATION -> updateNotification() + CLASSIC_NOTIFICATION -> { + updateNotification() + playingNotification?.setPlaying(isPlaying){ startForegroundOrNotify()} + playingNotification?.updateMetadata(currentSong) { startForegroundOrNotify() } + } + PLAYBACK_SPEED -> updateMediaSessionPlaybackState() + TOGGLE_HEADSET -> registerHeadsetEvents() + } + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent != null && intent.action != null) { + restoreQueuesAndPositionIfNecessary() + when (intent.action) { + ACTION_TOGGLE_PAUSE -> if (isPlaying) { + pause() + } else { + play() + } + ACTION_PAUSE -> pause() + ACTION_PLAY -> play() + ACTION_PLAY_PLAYLIST -> playFromPlaylist(intent) + ACTION_REWIND -> back(true) + ACTION_SKIP -> playNextSong(true) + ACTION_STOP, ACTION_QUIT -> { + pendingQuit = false + quit() + } + ACTION_PENDING_QUIT -> pendingQuit = true + TOGGLE_FAVORITE -> toggleFavorite(applicationContext, currentSong) + } + } + return START_NOT_STICKY + } + + override fun onTrackEnded() { + acquireWakeLock(30000) + playerHandler?.sendEmptyMessage(TRACK_ENDED) + } + + override fun onTrackEndedWithCrossfade() { + trackEndedByCrossfade = true + acquireWakeLock(30000) + playerHandler?.sendEmptyMessage(TRACK_ENDED) + } + + override fun onTrackWentToNext() { + playerHandler?.sendEmptyMessage(TRACK_WENT_TO_NEXT) + } + + override fun onUnbind(intent: Intent): Boolean { + if (!isPlaying) { + stopSelf() + } + return true + } + + fun openQueue( + playingQueue: List?, + startPosition: Int, + startPlaying: Boolean + ) { + if (playingQueue != null && playingQueue.isNotEmpty() + && startPosition >= 0 && startPosition < playingQueue.size + ) { + // it is important to copy the playing queue here first as we might add/remove songs later + originalPlayingQueue = ArrayList(playingQueue) + this.playingQueue = ArrayList(originalPlayingQueue) + var position = startPosition + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + makeShuffleList(this.playingQueue, startPosition) + position = 0 + } + if (startPlaying) { + playSongAt(position) + } else { + setPosition(position) + } + notifyChange(QUEUE_CHANGED) + } + } + + fun openTrackAndPrepareNextAt(position: Int): Boolean { + synchronized(this) { + this.position = position + val prepared = openCurrent() + if (prepared) { + prepareNextImpl() + } + notifyChange(META_CHANGED) + notHandledMetaChangedForCurrentTrack = false + return prepared + } + } + + fun pause() { + Log.i(TAG, "Paused") + isPausedByTransientLossOfFocus = false + if (playback != null && playback!!.isPlaying) { + startFadeAnimator(playback!!, false) { + + //Code to run when Animator Ends + playback?.pause() + notifyChange(PLAY_STATE_CHANGED) + } + } + } + + fun forcePause() { + isPausedByTransientLossOfFocus = false + if (playback != null && playback!!.isPlaying) { + playback?.pause() + notifyChange(PLAY_STATE_CHANGED) + } + } + + fun play() { + synchronized(this) { + if (requestFocus()) { + if (playback != null && !playback!!.isPlaying) { + if (!playback!!.isInitialized) { + playSongAt(getPosition()) + } else { + //Don't Start playing when it's casting + if (isCasting) { + return + } + startFadeAnimator(playback!!, true) { + + // Code when Animator Ends + if (!becomingNoisyReceiverRegistered) { + registerReceiver( + becomingNoisyReceiver, + becomingNoisyReceiverIntentFilter + ) + becomingNoisyReceiverRegistered = true + } + if (notHandledMetaChangedForCurrentTrack) { + handleChangeInternal(META_CHANGED) + notHandledMetaChangedForCurrentTrack = false + } + + // fixes a bug where the volume would stay ducked because the + // AudioManager.AUDIOFOCUS_GAIN event is not sent + playerHandler?.removeMessages(DUCK) + playerHandler?.sendEmptyMessage(UNDUCK) + } + //Start Playback with Animator + playback?.start() + notifyChange(PLAY_STATE_CHANGED) + } + } + } else { + Toast.makeText( + this, resources.getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT + ) + .show() + } + } + } + + fun playNextSong(force: Boolean) { + playSongAt(getNextPosition(force)) + } + + fun playPreviousSong(force: Boolean) { + playSongAt(getPreviousPosition(force)) + } + + fun playSongAt(position: Int) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler?.removeMessages(PLAY_SONG) + playerHandler?.obtainMessage(PLAY_SONG, position, 0)?.sendToTarget() + } + + fun playSongAtImpl(position: Int) { + if (!trackEndedByCrossfade) { + // This is only imp if we are using crossfade + if (playback is CrossFadePlayer) { + (playback as CrossFadePlayer).sourceChangedByUser() + } + } else { + trackEndedByCrossfade = false + } + if (openTrackAndPrepareNextAt(position)) { + play() + } else { + Toast.makeText(this, resources.getString(R.string.unplayable_file), Toast.LENGTH_SHORT) + .show() + } + } + + fun playSongs(songs: ArrayList?, shuffleMode: Int) { + if (songs != null && songs.isNotEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + val startPosition = Random().nextInt(songs.size) + openQueue(songs, startPosition, false) + setShuffleMode(shuffleMode) + } else { + openQueue(songs, 0, false) + } + play() + } else { + Toast.makeText(applicationContext, R.string.playlist_is_empty, Toast.LENGTH_LONG).show() + } + } + + fun prepareNextImpl() { + synchronized(this) { + try { + val nextPosition = getNextPosition(false) + playback?.setNextDataSource(getTrackUri(getSongAt(nextPosition))) + this.nextPosition = nextPosition + } catch (ignored: Exception) { + } + } + } + + fun quit() { + pause() + stopForeground(true) + notificationManager?.cancel(PlayingNotification.NOTIFICATION_ID) + closeAudioEffectSession() + audioManager?.abandonAudioFocus(audioFocusListener) + stopSelf() + } + + fun releaseWakeLock() { + if (wakeLock!!.isHeld) { + wakeLock?.release() + } + } + + fun removeSong(position: Int) { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + playingQueue.removeAt(position) + originalPlayingQueue.removeAt(position) + } else { + originalPlayingQueue.remove(playingQueue.removeAt(position)) + } + rePosition(position) + notifyChange(QUEUE_CHANGED) + } + + private fun removeSongImpl(song: Song) { + for (i in playingQueue.indices) { + if (playingQueue[i].id == song.id) { + playingQueue.removeAt(i) + rePosition(i) + } + } + for (i in originalPlayingQueue.indices) { + if (originalPlayingQueue[i].id == song.id) { + originalPlayingQueue.removeAt(i) + } + } + } + + fun removeSong(song: Song) { + removeSongImpl(song) + notifyChange(QUEUE_CHANGED) + } + + fun removeSongs(songs: List) { + for (song in songs) { + removeSongImpl(song) + } + notifyChange(QUEUE_CHANGED) + } + + @Synchronized + fun restoreQueuesAndPositionIfNecessary() { + if (!queuesRestored && playingQueue.isEmpty()) { + val restoredQueue = MusicPlaybackQueueStore.getInstance(this).savedPlayingQueue + val restoredOriginalQueue = + MusicPlaybackQueueStore.getInstance(this).savedOriginalPlayingQueue + val restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt( + SAVED_POSITION, -1 + ) + val restoredPositionInTrack = + PreferenceManager.getDefaultSharedPreferences(this).getInt( + SAVED_POSITION_IN_TRACK, -1 + ) + if (restoredQueue.size > 0 && restoredQueue.size == restoredOriginalQueue.size && restoredPosition != -1) { + originalPlayingQueue = restoredOriginalQueue + playingQueue = restoredQueue + position = restoredPosition + openCurrent() + prepareNext() + if (restoredPositionInTrack > 0) { + seek(restoredPositionInTrack) + } + notHandledMetaChangedForCurrentTrack = true + sendChangeInternal(META_CHANGED) + sendChangeInternal(QUEUE_CHANGED) + } + } + queuesRestored = true + } + + fun runOnUiThread(runnable: Runnable?) { + uiThreadHandler?.post(runnable!!) + } + + fun savePositionInTrack() { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION_IN_TRACK, songProgressMillis) + .apply() + } + + fun saveQueuesImpl() { + MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue) + } + + fun saveState() { + saveQueues() + savePosition() + savePositionInTrack() + } + + fun seek(millis: Int): Int { + synchronized(this) { + return try { + var newPosition = 0 + if (playback != null) { + newPosition = playback!!.seek(millis) + } + throttledSeekHandler?.notifySeek() + newPosition + } catch (e: Exception) { + -1 + } + } + } + + // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch + fun sendPublicIntent(what: String) { + val intent = Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)) + val song = currentSong + intent.putExtra("id", song.id) + intent.putExtra("artist", song.artistName) + intent.putExtra("album", song.albumName) + intent.putExtra("track", song.title) + intent.putExtra("duration", song.duration) + intent.putExtra("position", songProgressMillis.toLong()) + intent.putExtra("playing", isPlaying) + intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME) + sendStickyBroadcast(intent) + } + + fun toggleShuffle() { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + setShuffleMode(SHUFFLE_MODE_SHUFFLE) + } else { + setShuffleMode(SHUFFLE_MODE_NONE) + } + } + + fun updateMediaSessionPlaybackState() { + val stateBuilder = PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState( + if (isPlaying) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED, + songProgressMillis.toLong(), + playbackSpeed + ) + setCustomAction(stateBuilder) + mediaSession?.setPlaybackState(stateBuilder.build()) + } + + private fun updateNotification() { + if (playingNotification != null && currentSong.id != -1L) { + stopForegroundAndNotification() + initNotification() + } + } + + fun updateMediaSessionMetaData() { + Log.i(TAG, "onResourceReady: ") + val song = currentSong + if (song.id == -1L) { + mediaSession?.setMetadata(null) + return + } + val metaData = MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artistName) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, (getPosition() + 1).toLong()) + .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year.toLong()) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null) + metaData.putLong( + MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, + getPlayingQueue().size.toLong() + ) + if (isAlbumArtOnLockScreen) { + val screenSize = RetroUtil.getScreenSize(this@MusicService) + val request: RequestBuilder = + GlideApp.with(this@MusicService).asBitmap().songCoverOptions(song).load( + getSongModel(song) + ) + if (isBlurredAlbumArt) { + request.transform(BlurTransformation.Builder(this@MusicService).build()) + } + runOnUiThread { + request.into(object : SimpleTarget(screenSize.x, screenSize.y) { + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + mediaSession?.setMetadata(metaData.build()) + } + + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + metaData.putBitmap( + MediaMetadataCompat.METADATA_KEY_ALBUM_ART, + copy(resource) + ) + mediaSession?.setMetadata(metaData.build()) + } + }) + } + } else { + mediaSession?.setMetadata(metaData.build()) + } + } + + private fun closeAudioEffectSession() { + val audioEffectsIntent = Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION) + if (playback != null) { + audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback!!.audioSessionId) + } + audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName) + sendBroadcast(audioEffectsIntent) + } + + private fun handleChangeInternal(what: String) { + when (what) { + PLAY_STATE_CHANGED -> { + updateMediaSessionPlaybackState() + val isPlaying = isPlaying + if (!isPlaying && songProgressMillis > 0) { + savePositionInTrack() + } + songPlayCountHelper.notifyPlayStateChanged(isPlaying) + playingNotification?.setPlaying(isPlaying){ startForegroundOrNotify()} + startForegroundOrNotify() + } + FAVORITE_STATE_CHANGED -> { + playingNotification?.updateFavorite(currentSong) { startForegroundOrNotify() } + playingNotification?.updateMetadata(currentSong) { startForegroundOrNotify() } + updateMediaSessionMetaData() + updateMediaSessionPlaybackState() + savePosition() + savePositionInTrack() + val currentSong = currentSong + HistoryStore.getInstance(this).addSongId(currentSong.id) + if (songPlayCountHelper.shouldBumpPlayCount()) { + SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.song.id) + } + songPlayCountHelper.notifySongChanged(currentSong) + } + META_CHANGED -> { + playingNotification?.updateMetadata(currentSong) { startForegroundOrNotify() } + updateMediaSessionMetaData() + updateMediaSessionPlaybackState() + savePosition() + savePositionInTrack() + val currentSong = currentSong + HistoryStore.getInstance(this).addSongId(currentSong.id) + if (songPlayCountHelper.shouldBumpPlayCount()) { + SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.song.id) + } + songPlayCountHelper.notifySongChanged(currentSong) + } + QUEUE_CHANGED -> { + updateMediaSessionMetaData() // because playing queue size might have changed + saveState() + if (playingQueue.size > 0) { + prepareNext() + } else { + stopForegroundAndNotification() + } + } + } + } + + private fun startForegroundOrNotify() { + if (playingNotification != null && currentSong.id != -1L) { + val isPlaying = isPlaying + if (isForeground != isPlaying && !isPlaying) { + // This makes the notification dismissible + // We can't call stopForeground(false) on A12 though, which may result in crashes + // when we call startForeground after that e.g. when Alarm goes off, + if (VERSION.SDK_INT < VERSION_CODES.S) { + stopForeground(false) + isForeground = false + } + } + if (!isForeground && isPlaying) { + // Specify that this is a media service, if supported. + if (hasQ()) { + startForeground( + PlayingNotification.NOTIFICATION_ID, playingNotification!!.build(), + ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK + ) + } else { + startForeground( + PlayingNotification.NOTIFICATION_ID, + playingNotification!!.build() + ) + } + isForeground = true + } else { + // If we are already in foreground just update the notification + notificationManager?.notify( + PlayingNotification.NOTIFICATION_ID, playingNotification!!.build() + ) + } + } + return + } + + private fun stopForegroundAndNotification() { + stopForeground(true) + notificationManager?.cancel(PlayingNotification.NOTIFICATION_ID) + isForeground = false + } + + private fun openCurrent(): Boolean { + synchronized(this) { + try { + if (playback != null) { + return playback!!.setDataSource( + getTrackUri( + Objects.requireNonNull( + currentSong + ) + ) + ) + } + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + return false + } + + private fun playFromPlaylist(intent: Intent) { + val playlist: AbsSmartPlaylist? = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST) + val shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()) + if (playlist != null) { + val playlistSongs = playlist.songs() + if (playlistSongs.isNotEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + val startPosition = Random().nextInt(playlistSongs.size) + openQueue(playlistSongs, startPosition, true) + setShuffleMode(shuffleMode) + } else { + openQueue(playlistSongs, 0, true) + } + } else { + Toast.makeText(applicationContext, R.string.playlist_is_empty, Toast.LENGTH_LONG) + .show() + } + } else { + Toast.makeText(applicationContext, R.string.playlist_is_empty, Toast.LENGTH_LONG).show() + } + } + + private fun prepareNext() { + playerHandler?.removeMessages(PREPARE_NEXT) + playerHandler?.obtainMessage(PREPARE_NEXT)?.sendToTarget() + } + + private fun rePosition(deletedPosition: Int) { + val currentPosition = getPosition() + if (deletedPosition < currentPosition) { + position = currentPosition - 1 + } else if (deletedPosition == currentPosition) { + if (playingQueue.size > deletedPosition) { + setPosition(position) + } else { + setPosition(position - 1) + } + } + } + + private fun registerBluetoothConnected() { + Log.i(TAG, "registerBluetoothConnected: ") + if (!bluetoothConnectedRegistered) { + registerReceiver(bluetoothReceiver, bluetoothConnectedIntentFilter) + bluetoothConnectedRegistered = true + } + } + + private fun registerHeadsetEvents() { + if (!headsetReceiverRegistered && isHeadsetPlugged) { + registerReceiver(headsetReceiver, headsetReceiverIntentFilter) + headsetReceiverRegistered = true + } + } + + private fun releaseResources() { + playerHandler?.removeCallbacksAndMessages(null) + musicPlayerHandlerThread?.quitSafely() + queueSaveHandler?.removeCallbacksAndMessages(null) + queueSaveHandlerThread?.quitSafely() + if (playback != null) { + playback?.release() + } + playback = null + mediaSession?.release() + } + + private fun requestFocus(): Boolean { + return AudioManagerCompat.requestAudioFocus( + audioManager!!, + AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN) + .setOnAudioFocusChangeListener(audioFocusListener) + .setAudioAttributes( + AudioAttributesCompat.Builder().setContentType(CONTENT_TYPE_MUSIC).build() + ).build() + ) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED + } + + private fun restoreState() { + shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt( + SAVED_SHUFFLE_MODE, 0 + ) + repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt( + SAVED_REPEAT_MODE, 0 + ) + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED) + handleAndSendChangeInternal(REPEAT_MODE_CHANGED) + playerHandler?.removeMessages(RESTORE_QUEUES) + playerHandler?.sendEmptyMessage(RESTORE_QUEUES) + } + + private fun savePosition() { + PreferenceManager.getDefaultSharedPreferences(this) + .edit() + .putInt(SAVED_POSITION, getPosition()) + .apply() + } + + private fun saveQueues() { + queueSaveHandler?.removeMessages(SAVE_QUEUES) + queueSaveHandler?.sendEmptyMessage(SAVE_QUEUES) + } + + private fun sendChangeInternal(what: String) { + sendBroadcast(Intent(what)) + appWidgetBig.notifyChange(this, what) + appWidgetClassic.notifyChange(this, what) + appWidgetSmall.notifyChange(this, what) + appWidgetCard.notifyChange(this, what) + appWidgetText.notifyChange(this, what) + appWidgetMd3.notifyChange(this, what) + } + + private fun setCustomAction(stateBuilder: PlaybackStateCompat.Builder) { + var repeatIcon = R.drawable.ic_repeat // REPEAT_MODE_NONE + if (getRepeatMode() == REPEAT_MODE_THIS) { + repeatIcon = R.drawable.ic_repeat_one + } else if (getRepeatMode() == REPEAT_MODE_ALL) { + repeatIcon = R.drawable.ic_repeat_white_circle + } + stateBuilder.addCustomAction( + PlaybackStateCompat.CustomAction.Builder( + CYCLE_REPEAT, getString(R.string.action_cycle_repeat), repeatIcon + ) + .build() + ) + val shuffleIcon = + if (getShuffleMode() == SHUFFLE_MODE_NONE) R.drawable.ic_shuffle_off_circled else R.drawable.ic_shuffle_on_circled + stateBuilder.addCustomAction( + PlaybackStateCompat.CustomAction.Builder( + TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon + ) + .build() + ) + val favoriteIcon = if (isFavorite( + applicationContext, + currentSong + ) + ) R.drawable.ic_favorite else R.drawable.ic_favorite_border + stateBuilder.addCustomAction( + PlaybackStateCompat.CustomAction.Builder( + TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon + ) + .build() + ) + } + + private fun setupMediaSession() { + val mediaButtonReceiverComponentName = + ComponentName(applicationContext, MediaButtonIntentReceiver::class.java) + val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON) + mediaButtonIntent.component = mediaButtonReceiverComponentName + val mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast( + applicationContext, 0, mediaButtonIntent, + if (hasMarshmallow()) PendingIntent.FLAG_IMMUTABLE else 0 + ) + mediaSession = MediaSessionCompat( + this, + "RetroMusicPlayer", + mediaButtonReceiverComponentName, + mediaButtonReceiverPendingIntent + ) + val mediasessionCallback = MediaSessionCallback(applicationContext, this) + mediaSession?.setCallback(mediasessionCallback) + mediaSession?.isActive = true + mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent) + } + + inner class MusicBinder : Binder() { + val service: MusicService + get() = this@MusicService + } + + companion object { + val TAG: String = MusicService::class.java.simpleName + const val RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic" + const val MUSIC_PACKAGE_NAME = "com.android.music" + const val ACTION_TOGGLE_PAUSE = "$RETRO_MUSIC_PACKAGE_NAME.togglepause" + const val ACTION_PLAY = "$RETRO_MUSIC_PACKAGE_NAME.play" + const val ACTION_PLAY_PLAYLIST = "$RETRO_MUSIC_PACKAGE_NAME.play.playlist" + const val ACTION_PAUSE = "$RETRO_MUSIC_PACKAGE_NAME.pause" + const val ACTION_STOP = "$RETRO_MUSIC_PACKAGE_NAME.stop" + const val ACTION_SKIP = "$RETRO_MUSIC_PACKAGE_NAME.skip" + const val ACTION_REWIND = "$RETRO_MUSIC_PACKAGE_NAME.rewind" + const val ACTION_QUIT = "$RETRO_MUSIC_PACKAGE_NAME.quitservice" + const val ACTION_PENDING_QUIT = "$RETRO_MUSIC_PACKAGE_NAME.pendingquitservice" + const val INTENT_EXTRA_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist" + const val INTENT_EXTRA_SHUFFLE_MODE = "$RETRO_MUSIC_PACKAGE_NAME.intentextra.shufflemode" + const val APP_WIDGET_UPDATE = "$RETRO_MUSIC_PACKAGE_NAME.appreciate" + const val EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name" + + // Do not change these three strings as it will break support with other apps (e.g. last.fm + // scrobbling) + const val META_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.metachanged" + const val QUEUE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.queuechanged" + const val PLAY_STATE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.playstatechanged" + const val FAVORITE_STATE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.favoritestatechanged" + const val REPEAT_MODE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.repeatmodechanged" + const val SHUFFLE_MODE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.shufflemodechanged" + const val MEDIA_STORE_CHANGED = "$RETRO_MUSIC_PACKAGE_NAME.mediastorechanged" + const val CYCLE_REPEAT = "$RETRO_MUSIC_PACKAGE_NAME.cyclerepeat" + const val TOGGLE_SHUFFLE = "$RETRO_MUSIC_PACKAGE_NAME.toggleshuffle" + const val TOGGLE_FAVORITE = "$RETRO_MUSIC_PACKAGE_NAME.togglefavorite" + const val SAVED_POSITION = "POSITION" + const val SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK" + const val SAVED_SHUFFLE_MODE = "SHUFFLE_MODE" + const val SAVED_REPEAT_MODE = "REPEAT_MODE" + const val RELEASE_WAKELOCK = 0 + const val TRACK_ENDED = 1 + const val TRACK_WENT_TO_NEXT = 2 + const val PLAY_SONG = 3 + const val PREPARE_NEXT = 4 + const val SET_POSITION = 5 + const val FOCUS_CHANGE = 6 + const val DUCK = 7 + const val UNDUCK = 8 + const val RESTORE_QUEUES = 9 + const val SHUFFLE_MODE_NONE = 0 + const val SHUFFLE_MODE_SHUFFLE = 1 + const val REPEAT_MODE_NONE = 0 + const val REPEAT_MODE_ALL = 1 + const val REPEAT_MODE_THIS = 2 + const val SAVE_QUEUES = 0 + private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY + or PlaybackStateCompat.ACTION_PAUSE + or PlaybackStateCompat.ACTION_PLAY_PAUSE + or PlaybackStateCompat.ACTION_SKIP_TO_NEXT + or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + or PlaybackStateCompat.ACTION_STOP + or PlaybackStateCompat.ACTION_SEEK_TO) + + private fun copy(bitmap: Bitmap): Bitmap? { + var config = bitmap.config + if (config == null) { + config = Bitmap.Config.RGB_565 + } + return try { + bitmap.copy(config, false) + } catch (e: OutOfMemoryError) { + e.printStackTrace() + null + } + } + + private fun getTrackUri(song: Song): String { + return getSongFileUri(song.id).toString() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/QueueSaveHandler.kt b/app/src/main/java/code/name/monkey/retromusic/service/QueueSaveHandler.kt index 9abc31200..9fd1b91ad 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/QueueSaveHandler.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/QueueSaveHandler.kt @@ -17,7 +17,7 @@ package code.name.monkey.retromusic.service import android.os.Handler import android.os.Looper import android.os.Message -import code.name.monkey.retromusic.service.MusicService.SAVE_QUEUES +import code.name.monkey.retromusic.service.MusicService.Companion.SAVE_QUEUES import java.lang.ref.WeakReference internal class QueueSaveHandler( diff --git a/app/src/main/java/code/name/monkey/retromusic/service/ThrottledSeekHandler.kt b/app/src/main/java/code/name/monkey/retromusic/service/ThrottledSeekHandler.kt index 769ddb075..28b4bb8fd 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/ThrottledSeekHandler.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/ThrottledSeekHandler.kt @@ -16,8 +16,7 @@ package code.name.monkey.retromusic.service import android.os.Handler - -import code.name.monkey.retromusic.service.MusicService.PLAY_STATE_CHANGED +import code.name.monkey.retromusic.service.MusicService.Companion.PLAY_STATE_CHANGED class ThrottledSeekHandler( private val musicService: MusicService, diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationClassic.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationClassic.kt index 2588aebc8..23eee5435 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationClassic.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationClassic.kt @@ -39,7 +39,10 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_QUIT +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil.createBitmap diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.kt index 2f3f5aae6..7154190e1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.kt @@ -36,7 +36,11 @@ import code.name.monkey.retromusic.glide.RetroGlideExtension import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.service.MusicService -import code.name.monkey.retromusic.service.MusicService.* +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_QUIT +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_REWIND +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_SKIP +import code.name.monkey.retromusic.service.MusicService.Companion.ACTION_TOGGLE_PAUSE +import code.name.monkey.retromusic.service.MusicService.Companion.TOGGLE_FAVORITE import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.RetroColorUtil