766 lines
26 KiB
Kotlin
766 lines
26 KiB
Kotlin
@file:Suppress("DEPRECATION")
|
|
|
|
package io.legato.kazusa.service
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.app.PendingIntent
|
|
import android.content.BroadcastReceiver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.IntentFilter
|
|
import android.graphics.Bitmap
|
|
import android.graphics.BitmapFactory
|
|
import android.media.AudioManager
|
|
import android.net.wifi.WifiManager
|
|
import android.os.Bundle
|
|
import android.os.PowerManager
|
|
import android.support.v4.media.session.MediaSessionCompat
|
|
import android.support.v4.media.session.PlaybackStateCompat
|
|
import android.telephony.PhoneStateListener
|
|
import android.telephony.TelephonyManager
|
|
import androidx.annotation.CallSuper
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.media.AudioFocusRequestCompat
|
|
import androidx.media.AudioManagerCompat
|
|
import io.legato.kazusa.R
|
|
import io.legato.kazusa.base.BaseService
|
|
import io.legato.kazusa.constant.AppConst
|
|
import io.legato.kazusa.constant.AppLog
|
|
import io.legato.kazusa.constant.AppPattern
|
|
import io.legato.kazusa.constant.EventBus
|
|
import io.legato.kazusa.constant.IntentAction
|
|
import io.legato.kazusa.constant.NotificationId
|
|
import io.legato.kazusa.constant.PreferKey
|
|
import io.legato.kazusa.constant.Status
|
|
import io.legato.kazusa.help.MediaHelp
|
|
import io.legato.kazusa.help.config.AppConfig
|
|
import io.legato.kazusa.help.coroutine.Coroutine
|
|
import io.legato.kazusa.help.glide.ImageLoader
|
|
import io.legato.kazusa.lib.permission.Permissions
|
|
import io.legato.kazusa.lib.permission.PermissionsCompat
|
|
import io.legato.kazusa.model.ReadAloud
|
|
import io.legato.kazusa.model.ReadBook
|
|
import io.legato.kazusa.receiver.MediaButtonReceiver
|
|
import io.legato.kazusa.ui.book.read.ReadBookActivity
|
|
import io.legato.kazusa.ui.book.read.page.entities.TextChapter
|
|
import io.legato.kazusa.utils.LogUtils
|
|
import io.legato.kazusa.utils.activityPendingIntent
|
|
import io.legato.kazusa.utils.getPrefBoolean
|
|
import io.legato.kazusa.utils.observeEvent
|
|
import io.legato.kazusa.utils.observeSharedPreferences
|
|
import io.legato.kazusa.utils.postEvent
|
|
import io.legato.kazusa.utils.toastOnUi
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
import kotlinx.coroutines.Dispatchers.Main
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.isActive
|
|
import kotlinx.coroutines.launch
|
|
import splitties.init.appCtx
|
|
import splitties.systemservices.audioManager
|
|
import splitties.systemservices.notificationManager
|
|
import splitties.systemservices.powerManager
|
|
import splitties.systemservices.telephonyManager
|
|
import splitties.systemservices.wifiManager
|
|
|
|
/**
|
|
* 朗读服务
|
|
*/
|
|
abstract class BaseReadAloudService : BaseService(),
|
|
AudioManager.OnAudioFocusChangeListener {
|
|
|
|
companion object {
|
|
@JvmStatic
|
|
var isRun = false
|
|
private set
|
|
|
|
@JvmStatic
|
|
var pause = true
|
|
private set
|
|
|
|
@JvmStatic
|
|
var timeMinute: Int = 0
|
|
private set
|
|
|
|
fun isPlay(): Boolean {
|
|
return isRun && !pause
|
|
}
|
|
|
|
private const val TAG = "BaseReadAloudService"
|
|
|
|
}
|
|
|
|
private val useWakeLock = appCtx.getPrefBoolean(PreferKey.readAloudWakeLock, false)
|
|
private val wakeLock by lazy {
|
|
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "legado:ReadAloudService")
|
|
.apply {
|
|
this.setReferenceCounted(false)
|
|
}
|
|
}
|
|
private val wifiLock by lazy {
|
|
@Suppress("DEPRECATION")
|
|
wifiManager?.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "legado:AudioPlayService")
|
|
?.apply {
|
|
setReferenceCounted(false)
|
|
}
|
|
}
|
|
private val mFocusRequest: AudioFocusRequestCompat by lazy {
|
|
MediaHelp.buildAudioFocusRequestCompat(this)
|
|
}
|
|
private val mediaSessionCompat: MediaSessionCompat by lazy {
|
|
MediaSessionCompat(this, "readAloud")
|
|
}
|
|
private val phoneStateListener by lazy {
|
|
ReadAloudPhoneStateListener()
|
|
}
|
|
internal var contentList = emptyList<String>()
|
|
internal var nowSpeak: Int = 0
|
|
internal var readAloudNumber: Int = 0
|
|
internal var textChapter: TextChapter? = null
|
|
internal var pageIndex = 0
|
|
private var needResumeOnAudioFocusGain = false
|
|
private var needResumeOnCallStateIdle = false
|
|
private var registeredPhoneStateListener = false
|
|
private var dsJob: Job? = null
|
|
private var upNotificationJob: Coroutine<*>? = null
|
|
private var cover: Bitmap =
|
|
BitmapFactory.decodeResource(appCtx.resources, R.drawable.ic_launcher)
|
|
var pageChanged = false
|
|
private var toLast = false
|
|
var paragraphStartPos = 0
|
|
var readAloudByPage = false
|
|
private set
|
|
|
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
|
override fun onReceive(context: Context, intent: Intent) {
|
|
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {
|
|
pauseReadAloud()
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressLint("WakelockTimeout")
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
isRun = true
|
|
pause = false
|
|
observeLiveBus()
|
|
initMediaSession()
|
|
initBroadcastReceiver()
|
|
initPhoneStateListener()
|
|
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
|
|
setTimer(AppConfig.ttsTimer)
|
|
if (AppConfig.ttsTimer > 0) {
|
|
toastOnUi("朗读定时 ${AppConfig.ttsTimer} 分钟")
|
|
}
|
|
execute {
|
|
ImageLoader
|
|
.loadBitmap(this@BaseReadAloudService, ReadBook.book?.getDisplayCover())
|
|
.submit()
|
|
.get()
|
|
}.onSuccess {
|
|
if (it.width > 16 && it.height > 16) {
|
|
cover = it
|
|
upReadAloudNotification()
|
|
}
|
|
}
|
|
}
|
|
|
|
fun observeLiveBus() {
|
|
observeEvent<Bundle>(EventBus.READ_ALOUD_PLAY) {
|
|
val play = it.getBoolean("play")
|
|
val pageIndex = it.getInt("pageIndex")
|
|
val startPos = it.getInt("startPos")
|
|
newReadAloud(play, pageIndex, startPos)
|
|
}
|
|
observeSharedPreferences { _, key ->
|
|
when (key) {
|
|
PreferKey.ignoreAudioFocus,
|
|
PreferKey.pauseReadAloudWhilePhoneCalls -> {
|
|
initPhoneStateListener()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
if (useWakeLock) {
|
|
wakeLock.release()
|
|
wifiLock?.release()
|
|
}
|
|
isRun = false
|
|
pause = true
|
|
abandonFocus()
|
|
unregisterReceiver(broadcastReceiver)
|
|
postEvent(EventBus.ALOUD_STATE, Status.STOP)
|
|
notificationManager.cancel(NotificationId.ReadAloudService)
|
|
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED)
|
|
mediaSessionCompat.release()
|
|
ReadBook.uploadProgress()
|
|
unregisterPhoneStateListener(phoneStateListener)
|
|
upNotificationJob?.invokeOnCompletion {
|
|
notificationManager.cancel(NotificationId.ReadAloudService)
|
|
}
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
when (intent?.action) {
|
|
IntentAction.play -> newReadAloud(
|
|
intent.getBooleanExtra("play", true),
|
|
intent.getIntExtra("pageIndex", ReadBook.durPageIndex),
|
|
intent.getIntExtra("startPos", 0)
|
|
)
|
|
|
|
IntentAction.pause -> pauseReadAloud()
|
|
IntentAction.resume -> resumeReadAloud()
|
|
IntentAction.upTtsSpeechRate -> upSpeechRate(true)
|
|
IntentAction.prevParagraph -> prevP()
|
|
IntentAction.nextParagraph -> nextP()
|
|
IntentAction.prev -> prevChapter()
|
|
IntentAction.next -> nextChapter()
|
|
IntentAction.addTimer -> addTimer()
|
|
IntentAction.setTimer -> setTimer(intent.getIntExtra("minute", 0))
|
|
IntentAction.stop -> stopSelf()
|
|
}
|
|
return super.onStartCommand(intent, flags, startId)
|
|
}
|
|
|
|
private fun newReadAloud(play: Boolean, pageIndex: Int, startPos: Int) {
|
|
execute(executeContext = IO) {
|
|
this@BaseReadAloudService.pageIndex = pageIndex
|
|
textChapter = ReadBook.curTextChapter
|
|
val textChapter = textChapter ?: return@execute
|
|
if (!textChapter.isCompleted) {
|
|
return@execute
|
|
}
|
|
readAloudNumber = textChapter.getReadLength(pageIndex) + startPos
|
|
readAloudByPage = getPrefBoolean(PreferKey.readAloudByPage)
|
|
contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0)
|
|
.split("\n")
|
|
.filter { it.isNotEmpty() }
|
|
var pos = startPos
|
|
val page = textChapter.getPage(pageIndex)!!
|
|
if (pos > 0) {
|
|
for (paragraph in page.paragraphs) {
|
|
val tmp = pos - paragraph.length - 1
|
|
if (tmp < 0) break
|
|
pos = tmp
|
|
}
|
|
}
|
|
nowSpeak = textChapter.getParagraphNum(readAloudNumber + 1, readAloudByPage) - 1
|
|
if (!readAloudByPage && startPos == 0 && !toLast) {
|
|
pos = page.chapterPosition -
|
|
textChapter.paragraphs[nowSpeak].chapterPosition
|
|
}
|
|
if (toLast) {
|
|
toLast = false
|
|
readAloudNumber = textChapter.getLastParagraphPosition()
|
|
nowSpeak = contentList.lastIndex
|
|
if (page.paragraphs.size == 1) {
|
|
pos = page.chapterPosition -
|
|
textChapter.paragraphs[nowSpeak].chapterPosition
|
|
}
|
|
}
|
|
paragraphStartPos = pos
|
|
launch(Main) {
|
|
if (play) play() else pageChanged = true
|
|
}
|
|
}.onError {
|
|
AppLog.put("启动朗读出错\n${it.localizedMessage}", it, true)
|
|
}
|
|
}
|
|
|
|
@SuppressLint("WakelockTimeout")
|
|
open fun play() {
|
|
if (useWakeLock) {
|
|
wakeLock.acquire()
|
|
wifiLock?.acquire()
|
|
}
|
|
isRun = true
|
|
pause = false
|
|
needResumeOnAudioFocusGain = false
|
|
needResumeOnCallStateIdle = false
|
|
upReadAloudNotification()
|
|
postEvent(EventBus.ALOUD_STATE, Status.PLAY)
|
|
}
|
|
|
|
abstract fun playStop()
|
|
|
|
@CallSuper
|
|
open fun pauseReadAloud(abandonFocus: Boolean = true) {
|
|
if (useWakeLock) {
|
|
wakeLock.release()
|
|
wifiLock?.release()
|
|
}
|
|
pause = true
|
|
if (abandonFocus) {
|
|
abandonFocus()
|
|
}
|
|
upReadAloudNotification()
|
|
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED)
|
|
postEvent(EventBus.ALOUD_STATE, Status.PAUSE)
|
|
ReadBook.uploadProgress()
|
|
doDs()
|
|
}
|
|
|
|
@SuppressLint("WakelockTimeout")
|
|
@CallSuper
|
|
open fun resumeReadAloud() {
|
|
pause = false
|
|
needResumeOnAudioFocusGain = false
|
|
needResumeOnCallStateIdle = false
|
|
upReadAloudNotification()
|
|
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
|
|
postEvent(EventBus.ALOUD_STATE, Status.PLAY)
|
|
}
|
|
|
|
abstract fun upSpeechRate(reset: Boolean = false)
|
|
|
|
fun upTtsProgress(progress: Int) {
|
|
postEvent(EventBus.TTS_PROGRESS, progress)
|
|
}
|
|
|
|
private fun prevP() {
|
|
if (nowSpeak > 0) {
|
|
playStop()
|
|
do {
|
|
nowSpeak--
|
|
readAloudNumber -= contentList[nowSpeak].length + 1 + paragraphStartPos
|
|
paragraphStartPos = 0
|
|
} while (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex))
|
|
textChapter?.let {
|
|
if (readAloudByPage) {
|
|
val paragraphs = it.getParagraphs(true)
|
|
if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber++
|
|
}
|
|
if (readAloudNumber < it.getReadLength(pageIndex)) {
|
|
pageIndex--
|
|
ReadBook.moveToPrevPage()
|
|
}
|
|
}
|
|
upTtsProgress(readAloudNumber + 1)
|
|
play()
|
|
} else {
|
|
toLast = true
|
|
ReadBook.moveToPrevChapter(true)
|
|
}
|
|
}
|
|
|
|
private fun nextP() {
|
|
if (nowSpeak < contentList.size - 1) {
|
|
playStop()
|
|
readAloudNumber += contentList[nowSpeak].length.plus(1) - paragraphStartPos
|
|
paragraphStartPos = 0
|
|
nowSpeak++
|
|
textChapter?.let {
|
|
if (readAloudByPage) {
|
|
val paragraphs = it.getParagraphs(true)
|
|
if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber--
|
|
}
|
|
if (pageIndex + 1 < it.pageSize
|
|
&& readAloudNumber >= it.getReadLength(pageIndex + 1)
|
|
) {
|
|
pageIndex++
|
|
ReadBook.moveToNextPage()
|
|
}
|
|
}
|
|
upTtsProgress(readAloudNumber + 1)
|
|
play()
|
|
} else {
|
|
nextChapter()
|
|
}
|
|
}
|
|
|
|
private fun setTimer(minute: Int) {
|
|
timeMinute = minute
|
|
doDs()
|
|
}
|
|
|
|
private fun addTimer() {
|
|
if (timeMinute == 180) {
|
|
timeMinute = 0
|
|
} else {
|
|
timeMinute += 10
|
|
if (timeMinute > 180) timeMinute = 180
|
|
}
|
|
doDs()
|
|
}
|
|
|
|
/**
|
|
* 定时
|
|
*/
|
|
@Synchronized
|
|
private fun doDs() {
|
|
postEvent(EventBus.READ_ALOUD_DS, timeMinute)
|
|
upReadAloudNotification()
|
|
dsJob?.cancel()
|
|
dsJob = lifecycleScope.launch {
|
|
while (isActive) {
|
|
delay(60000)
|
|
if (!pause) {
|
|
if (timeMinute >= 0) {
|
|
timeMinute--
|
|
}
|
|
if (timeMinute == 0) {
|
|
ReadAloud.stop(this@BaseReadAloudService)
|
|
postEvent(EventBus.READ_ALOUD_DS, timeMinute)
|
|
break
|
|
}
|
|
}
|
|
postEvent(EventBus.READ_ALOUD_DS, timeMinute)
|
|
upReadAloudNotification()
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 请求音频焦点
|
|
* @return 音频焦点
|
|
*/
|
|
fun requestFocus(): Boolean {
|
|
if (AppConfig.ignoreAudioFocus) {
|
|
return true
|
|
}
|
|
val requestFocus = MediaHelp.requestFocus(mFocusRequest)
|
|
if (!requestFocus) {
|
|
pauseReadAloud(false)
|
|
toastOnUi("未获取到音频焦点")
|
|
}
|
|
return requestFocus
|
|
}
|
|
|
|
/**
|
|
* 放弃音频焦点
|
|
*/
|
|
private fun abandonFocus() {
|
|
AudioManagerCompat.abandonAudioFocusRequest(audioManager, mFocusRequest)
|
|
}
|
|
|
|
/**
|
|
* 更新媒体状态
|
|
*/
|
|
private fun upMediaSessionPlaybackState(state: Int) {
|
|
mediaSessionCompat.setPlaybackState(
|
|
PlaybackStateCompat.Builder()
|
|
.setActions(MediaHelp.MEDIA_SESSION_ACTIONS)
|
|
.setState(state, nowSpeak.toLong(), 1f)
|
|
// 为系统媒体控件添加定时按钮
|
|
// .addCustomAction(
|
|
// PlaybackStateCompat.CustomAction.Builder(
|
|
// "ACTION_ADD_TIMER",
|
|
// getString(R.string.set_timer),
|
|
// R.drawable.ic_time_add_24dp
|
|
// ).build()
|
|
// )
|
|
.build()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 初始化MediaSession, 注册多媒体按钮
|
|
*/
|
|
/**
|
|
* 初始化MediaSession, 注册多媒体按钮
|
|
*/
|
|
@SuppressLint("UnspecifiedImmutableFlag")
|
|
private fun initMediaSession() {
|
|
if (getPrefBoolean("systemMediaControlCompatibilityChange")) {
|
|
mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
|
|
override fun onPlay() {
|
|
resumeReadAloud()
|
|
}
|
|
|
|
override fun onPause() {
|
|
pauseReadAloud()
|
|
}
|
|
|
|
override fun onSkipToNext() {
|
|
if (getPrefBoolean("mediaButtonPerNext", false)) {
|
|
nextChapter()
|
|
} else {
|
|
nextP()
|
|
}
|
|
}
|
|
|
|
override fun onSkipToPrevious() {
|
|
if (getPrefBoolean("mediaButtonPerNext", false)) {
|
|
prevChapter()
|
|
} else {
|
|
prevP()
|
|
}
|
|
}
|
|
|
|
override fun onStop() {
|
|
stopSelf()
|
|
}
|
|
|
|
override fun onCustomAction(action: String, extras: Bundle?) {
|
|
if (action == "ACTION_ADD_TIMER") addTimer()
|
|
}
|
|
|
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
|
return MediaButtonReceiver.handleIntent(
|
|
this@BaseReadAloudService, mediaButtonEvent
|
|
)
|
|
}
|
|
})
|
|
} else {
|
|
mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
|
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
|
return MediaButtonReceiver.handleIntent(
|
|
this@BaseReadAloudService, mediaButtonEvent
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 注册多媒体按钮监听
|
|
*/
|
|
private fun initBroadcastReceiver() {
|
|
val intentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
|
registerReceiver(broadcastReceiver, intentFilter)
|
|
}
|
|
|
|
/**
|
|
* 音频焦点变化
|
|
*/
|
|
override fun onAudioFocusChange(focusChange: Int) {
|
|
if (AppConfig.ignoreAudioFocus) {
|
|
AppLog.put("忽略音频焦点处理(TTS)")
|
|
return
|
|
}
|
|
when (focusChange) {
|
|
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
if (needResumeOnAudioFocusGain) {
|
|
AppLog.put("音频焦点获得,继续朗读")
|
|
resumeReadAloud()
|
|
} else {
|
|
AppLog.put("音频焦点获得")
|
|
}
|
|
}
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
AppLog.put("音频焦点丢失,暂停朗读")
|
|
pauseReadAloud()
|
|
}
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
AppLog.put("音频焦点暂时丢失并会很快再次获得,暂停朗读")
|
|
if (!pause) {
|
|
needResumeOnAudioFocusGain = true
|
|
pauseReadAloud(false)
|
|
}
|
|
}
|
|
|
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
|
// 短暂丢失焦点,这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量(或者关闭声音)凸显这个声音(比如短信提示音),
|
|
AppLog.put("音频焦点短暂丢失,不做处理")
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun upReadAloudNotification() {
|
|
upNotificationJob = execute {
|
|
try {
|
|
val notification = createNotification()
|
|
notificationManager.notify(NotificationId.ReadAloudService, notification.build())
|
|
} catch (e: Exception) {
|
|
AppLog.put("创建朗读通知出错,${e.localizedMessage}", e, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun choiceMediaStyle(): androidx.media.app.NotificationCompat.MediaStyle {
|
|
val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()
|
|
.setShowActionsInCompactView(1, 2, 4)
|
|
if (getPrefBoolean("systemMediaControlCompatibilityChange")) {
|
|
//fix #4090 android 14 can not show play control in lock screen
|
|
mediaStyle.setMediaSession(mediaSessionCompat.sessionToken)
|
|
}
|
|
return mediaStyle
|
|
}
|
|
|
|
private fun createNotification(): NotificationCompat.Builder {
|
|
var nTitle: String = when {
|
|
pause -> getString(R.string.read_aloud_pause)
|
|
timeMinute > 0 -> getString(
|
|
R.string.read_aloud_timer,
|
|
timeMinute
|
|
)
|
|
|
|
else -> getString(R.string.read_aloud_t)
|
|
}
|
|
nTitle += ": ${ReadBook.book?.name}"
|
|
var nSubtitle = ReadBook.curTextChapter?.title
|
|
if (nSubtitle.isNullOrBlank())
|
|
nSubtitle = getString(R.string.read_aloud_s)
|
|
val builder = NotificationCompat
|
|
.Builder(this, AppConst.channelIdReadAloud)
|
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
|
.setCategory(NotificationCompat.CATEGORY_TRANSPORT)
|
|
.setSmallIcon(R.drawable.ic_volume_up)
|
|
.setSubText(getString(R.string.read_aloud))
|
|
.setOngoing(true)
|
|
.setOnlyAlertOnce(true)
|
|
.setContentTitle(nTitle)
|
|
.setContentText(nSubtitle)
|
|
.setContentIntent(
|
|
activityPendingIntent<ReadBookActivity>("activity")
|
|
)
|
|
.setVibrate(null)
|
|
.setSound(null)
|
|
.setLights(0, 0, 0)
|
|
builder.setLargeIcon(cover)
|
|
// 按钮定义:上一章、播放、停止、下一章、定时
|
|
builder.addAction(
|
|
R.drawable.ic_skip_previous,
|
|
getString(R.string.previous_chapter),
|
|
aloudServicePendingIntent(IntentAction.prev)
|
|
)
|
|
if (pause) {
|
|
builder.addAction(
|
|
R.drawable.ic_play,
|
|
getString(R.string.resume),
|
|
aloudServicePendingIntent(IntentAction.resume)
|
|
)
|
|
} else {
|
|
builder.addAction(
|
|
R.drawable.ic_pause,
|
|
getString(R.string.pause),
|
|
aloudServicePendingIntent(IntentAction.pause)
|
|
)
|
|
}
|
|
builder.addAction(
|
|
R.drawable.ic_stop_black_24dp,
|
|
getString(R.string.stop),
|
|
aloudServicePendingIntent(IntentAction.stop)
|
|
)
|
|
builder.addAction(
|
|
R.drawable.ic_skip_next,
|
|
getString(R.string.next_chapter),
|
|
aloudServicePendingIntent(IntentAction.next)
|
|
)
|
|
builder.addAction(
|
|
R.drawable.ic_time_add_24dp,
|
|
getString(R.string.set_timer),
|
|
aloudServicePendingIntent(IntentAction.addTimer)
|
|
)
|
|
builder.setStyle(choiceMediaStyle())
|
|
return builder
|
|
}
|
|
|
|
/**
|
|
* 更新通知
|
|
*/
|
|
override fun startForegroundNotification() {
|
|
execute {
|
|
try {
|
|
val notification = createNotification()
|
|
startForeground(NotificationId.ReadAloudService, notification.build())
|
|
} catch (e: Exception) {
|
|
AppLog.put("创建朗读通知出错,${e.localizedMessage}", e, true)
|
|
//创建通知出错不结束服务就会崩溃,服务必须绑定通知
|
|
stopSelf()
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract fun aloudServicePendingIntent(actionStr: String): PendingIntent?
|
|
|
|
open fun prevChapter() {
|
|
toLast = false
|
|
ReadBook.moveToPrevChapter(true, toLast = false)
|
|
}
|
|
|
|
open fun nextChapter() {
|
|
ReadBook.upReadTime()
|
|
AppLog.putDebug("${ReadBook.curTextChapter?.chapter?.title} 朗读结束跳转下一章并朗读")
|
|
if (!ReadBook.moveToNextChapter(true)) {
|
|
stopSelf()
|
|
}
|
|
}
|
|
|
|
private fun initPhoneStateListener() {
|
|
val needRegister = AppConfig.ignoreAudioFocus && AppConfig.pauseReadAloudWhilePhoneCalls
|
|
if (needRegister && registeredPhoneStateListener) {
|
|
return
|
|
}
|
|
if (needRegister) {
|
|
registerPhoneStateListener(phoneStateListener)
|
|
} else {
|
|
unregisterPhoneStateListener(phoneStateListener)
|
|
}
|
|
}
|
|
|
|
private fun unregisterPhoneStateListener(l: PhoneStateListener) {
|
|
if (registeredPhoneStateListener) {
|
|
withReadPhoneStatePermission {
|
|
telephonyManager.listen(l, PhoneStateListener.LISTEN_NONE)
|
|
registeredPhoneStateListener = false
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun registerPhoneStateListener(l: PhoneStateListener) {
|
|
withReadPhoneStatePermission {
|
|
telephonyManager.listen(l, PhoneStateListener.LISTEN_CALL_STATE)
|
|
registeredPhoneStateListener = true
|
|
}
|
|
}
|
|
|
|
private fun withReadPhoneStatePermission(block: () -> Unit) {
|
|
try {
|
|
block.invoke()
|
|
} catch (_: SecurityException) {
|
|
PermissionsCompat.Builder()
|
|
.addPermissions(Permissions.READ_PHONE_STATE)
|
|
.rationale(R.string.read_aloud_read_phone_state_permission_rationale)
|
|
.onGranted {
|
|
try {
|
|
block.invoke()
|
|
} catch (_: SecurityException) {
|
|
LogUtils.d(TAG, "Grant read phone state permission fail.")
|
|
}
|
|
}
|
|
.request()
|
|
}
|
|
}
|
|
|
|
@Suppress("OVERRIDE_DEPRECATION")
|
|
inner class ReadAloudPhoneStateListener : PhoneStateListener() {
|
|
override fun onCallStateChanged(state: Int, phoneNumber: String?) {
|
|
super.onCallStateChanged(state, phoneNumber)
|
|
when (state) {
|
|
TelephonyManager.CALL_STATE_IDLE -> {
|
|
if (needResumeOnCallStateIdle) {
|
|
AppLog.put("来电结束,继续朗读")
|
|
resumeReadAloud()
|
|
} else {
|
|
AppLog.put("来电结束")
|
|
}
|
|
}
|
|
|
|
TelephonyManager.CALL_STATE_RINGING -> {
|
|
if (!pause) {
|
|
AppLog.put("来电响铃,暂停朗读")
|
|
needResumeOnCallStateIdle = true
|
|
pauseReadAloud()
|
|
} else {
|
|
AppLog.put("来电响铃")
|
|
}
|
|
}
|
|
|
|
TelephonyManager.CALL_STATE_OFFHOOK -> {
|
|
AppLog.put("来电接听,不做处理")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|