Revert "Use MediaButtonReceiver from androidx.media to handle headset button actions"
This reverts commit ad51d09672
.
This commit is contained in:
parent
192b5c2ac7
commit
e4a6906231
3 changed files with 218 additions and 8 deletions
|
@ -34,12 +34,12 @@
|
||||||
android:configChanges="locale|layoutDirection"
|
android:configChanges="locale|layoutDirection"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:restoreAnyVersion="true"
|
android:restoreAnyVersion="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.RetroMusic.FollowSystem"
|
android:theme="@style/Theme.RetroMusic.FollowSystem"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
tools:targetApi="m">
|
tools:targetApi="m">
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
|
@ -194,7 +194,7 @@
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="androidx.media.session.MediaButtonReceiver"
|
android:name=".service.MediaButtonIntentReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
@ -305,9 +305,6 @@
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* 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.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Message
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.os.PowerManager.WakeLock
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.media.session.MediaButtonReceiver
|
||||||
|
import code.name.monkey.retromusic.BuildConfig
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to control headset playback.
|
||||||
|
* Single press: pause/resume
|
||||||
|
* Double press: actionNext track
|
||||||
|
* Triple press: previous track
|
||||||
|
*/
|
||||||
|
class MediaButtonIntentReceiver : MediaButtonReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Received intent: $intent")
|
||||||
|
if (handleIntent(context, intent) && isOrderedBroadcast) {
|
||||||
|
abortBroadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TAG: String = MediaButtonIntentReceiver::class.java.simpleName
|
||||||
|
private val DEBUG = BuildConfig.DEBUG
|
||||||
|
private const val MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2
|
||||||
|
|
||||||
|
private const val DOUBLE_CLICK = 400
|
||||||
|
|
||||||
|
private var wakeLock: WakeLock? = null
|
||||||
|
private var mClickCounter = 0
|
||||||
|
private var mLastClickTime: Long = 0
|
||||||
|
|
||||||
|
@SuppressLint("HandlerLeak") // false alarm, handler is already static
|
||||||
|
private val mHandler = object : Handler() {
|
||||||
|
|
||||||
|
override fun handleMessage(msg: Message) {
|
||||||
|
when (msg.what) {
|
||||||
|
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT -> {
|
||||||
|
val clickCount = msg.arg1
|
||||||
|
|
||||||
|
if (DEBUG) Log.v(TAG, "Handling headset click, count = $clickCount")
|
||||||
|
val command = when (clickCount) {
|
||||||
|
1 -> ACTION_TOGGLE_PAUSE
|
||||||
|
2 -> ACTION_SKIP
|
||||||
|
3 -> ACTION_REWIND
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command != null) {
|
||||||
|
val context = msg.obj as Context
|
||||||
|
startService(context, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
releaseWakeLockIfHandlerIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleIntent(context: Context, intent: Intent): Boolean {
|
||||||
|
println("Intent Action: ${intent.action}")
|
||||||
|
val intentAction = intent.action
|
||||||
|
if (Intent.ACTION_MEDIA_BUTTON == intentAction) {
|
||||||
|
val event = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
|
||||||
|
?: return false
|
||||||
|
|
||||||
|
val keycode = event.keyCode
|
||||||
|
val action = event.action
|
||||||
|
val eventTime = if (event.eventTime != 0L)
|
||||||
|
event.eventTime
|
||||||
|
else
|
||||||
|
System.currentTimeMillis()
|
||||||
|
|
||||||
|
var command: String? = null
|
||||||
|
when (keycode) {
|
||||||
|
KeyEvent.KEYCODE_MEDIA_STOP -> command = ACTION_STOP
|
||||||
|
KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> command =
|
||||||
|
ACTION_TOGGLE_PAUSE
|
||||||
|
KeyEvent.KEYCODE_MEDIA_NEXT -> command = ACTION_SKIP
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> command = ACTION_REWIND
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PAUSE -> command = ACTION_PAUSE
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PLAY -> command = ACTION_PLAY
|
||||||
|
}
|
||||||
|
if (command != null) {
|
||||||
|
if (action == KeyEvent.ACTION_DOWN) {
|
||||||
|
if (event.repeatCount == 0) {
|
||||||
|
// Only consider the first event in a sequence, not the repeat events,
|
||||||
|
// so that we don't trigger in cases where the first event went to
|
||||||
|
// a different app (e.g. when the user ends a phone call by
|
||||||
|
// long pressing the headset button)
|
||||||
|
|
||||||
|
// The service may or may not be running, but we need to send it
|
||||||
|
// a command.
|
||||||
|
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
|
||||||
|
if (eventTime - mLastClickTime >= DOUBLE_CLICK) {
|
||||||
|
mClickCounter = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mClickCounter++
|
||||||
|
if (DEBUG) Log.v(TAG, "Got headset click, count = $mClickCounter")
|
||||||
|
mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)
|
||||||
|
|
||||||
|
val msg = mHandler.obtainMessage(
|
||||||
|
MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context
|
||||||
|
)
|
||||||
|
|
||||||
|
val delay = (if (mClickCounter < 3) DOUBLE_CLICK else 0).toLong()
|
||||||
|
if (mClickCounter >= 3) {
|
||||||
|
mClickCounter = 0
|
||||||
|
}
|
||||||
|
mLastClickTime = eventTime
|
||||||
|
acquireWakeLockAndSendMessage(context, msg, delay)
|
||||||
|
} else {
|
||||||
|
startService(context, command)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startService(context: Context, command: String?) {
|
||||||
|
val intent = Intent(context, MusicService::class.java)
|
||||||
|
intent.action = command
|
||||||
|
try {
|
||||||
|
// IMPORTANT NOTE: (kind of a hack)
|
||||||
|
// on Android O and above the following crashes when the app is not running
|
||||||
|
// there is no good way to check whether the app is running so we catch the exception
|
||||||
|
// we do not always want to use startForegroundService() because then one gets an ANR
|
||||||
|
// if no notification is displayed via startForeground()
|
||||||
|
// according to Play analytics this happens a lot, I suppose for example if command = PAUSE
|
||||||
|
context.startService(intent)
|
||||||
|
} catch (ignored: IllegalStateException) {
|
||||||
|
ContextCompat.startForegroundService(context, intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun acquireWakeLockAndSendMessage(context: Context, msg: Message, delay: Long) {
|
||||||
|
if (wakeLock == null) {
|
||||||
|
val appContext = context.applicationContext
|
||||||
|
val pm = appContext.getSystemService<PowerManager>()
|
||||||
|
wakeLock = pm?.newWakeLock(
|
||||||
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
"RetroMusicApp:Wakelock headset button"
|
||||||
|
)
|
||||||
|
wakeLock!!.setReferenceCounted(false)
|
||||||
|
}
|
||||||
|
if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what)
|
||||||
|
// Make sure we don't indefinitely hold the wake lock under any circumstances
|
||||||
|
wakeLock!!.acquire(10000)
|
||||||
|
|
||||||
|
mHandler.sendMessageDelayed(msg, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releaseWakeLockIfHandlerIdle() {
|
||||||
|
if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wakeLock != null) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Releasing wake lock")
|
||||||
|
wakeLock!!.release()
|
||||||
|
wakeLock = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ package code.name.monkey.retromusic.service
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.bluetooth.BluetoothDevice.EXTRA_DEVICE
|
import android.bluetooth.BluetoothDevice.EXTRA_DEVICE
|
||||||
|
@ -39,7 +40,6 @@ import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.media.MediaBrowserServiceCompat
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
import androidx.media.session.MediaButtonReceiver.handleIntent
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||||
import code.name.monkey.retromusic.*
|
import code.name.monkey.retromusic.*
|
||||||
|
@ -94,6 +94,7 @@ import kotlinx.coroutines.Dispatchers.Main
|
||||||
import org.koin.java.KoinJavaComponent.get
|
import org.koin.java.KoinJavaComponent.get
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More
|
* @author Karim Abou Zeid (kabouzeid), Andrew Neal. Modified by Prathamesh More
|
||||||
*/
|
*/
|
||||||
|
@ -649,7 +650,6 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent != null && intent.action != null) {
|
if (intent != null && intent.action != null) {
|
||||||
handleIntent(mediaSession, intent)
|
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
restoreQueuesAndPositionIfNecessary()
|
restoreQueuesAndPositionIfNecessary()
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
|
@ -1305,13 +1305,25 @@ class MusicService : MediaBrowserServiceCompat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupMediaSession() {
|
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 (VersionUtils.hasMarshmallow()) PendingIntent.FLAG_IMMUTABLE else 0
|
||||||
|
)
|
||||||
mediaSession = MediaSessionCompat(
|
mediaSession = MediaSessionCompat(
|
||||||
this,
|
this,
|
||||||
"RetroMusicPlayer"
|
BuildConfig.APPLICATION_ID,
|
||||||
|
mediaButtonReceiverComponentName,
|
||||||
|
mediaButtonReceiverPendingIntent
|
||||||
)
|
)
|
||||||
val mediaSessionCallback = MediaSessionCallback(this)
|
val mediaSessionCallback = MediaSessionCallback(this)
|
||||||
mediaSession?.setCallback(mediaSessionCallback)
|
mediaSession?.setCallback(mediaSessionCallback)
|
||||||
mediaSession?.isActive = true
|
mediaSession?.isActive = true
|
||||||
|
mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class MusicBinder : Binder() {
|
inner class MusicBinder : Binder() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue