diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 697a4cb6f..efbc081f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,12 +34,12 @@ android:configChanges="locale|layoutDirection" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:requestLegacyExternalStorage="true" android:restoreAnyVersion="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.RetroMusic.FollowSystem" android:usesCleartextTraffic="true" - android:requestLegacyExternalStorage="true" tools:targetApi="m"> @@ -305,9 +305,6 @@ - - - { + 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(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() + 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 + } + } + } +} \ 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 index d4ac50db0..c45d9b83e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.kt @@ -15,6 +15,7 @@ package code.name.monkey.retromusic.service import android.annotation.SuppressLint import android.app.NotificationManager +import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice.EXTRA_DEVICE @@ -39,7 +40,6 @@ import android.widget.Toast import androidx.core.content.edit import androidx.core.content.getSystemService import androidx.media.MediaBrowserServiceCompat -import androidx.media.session.MediaButtonReceiver.handleIntent import androidx.preference.PreferenceManager import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.* @@ -94,6 +94,7 @@ import kotlinx.coroutines.Dispatchers.Main import org.koin.java.KoinJavaComponent.get import java.util.* + /** * @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 { if (intent != null && intent.action != null) { - handleIntent(mediaSession, intent) serviceScope.launch { restoreQueuesAndPositionIfNecessary() when (intent.action) { @@ -1305,13 +1305,25 @@ class MusicService : MediaBrowserServiceCompat(), } 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( this, - "RetroMusicPlayer" + BuildConfig.APPLICATION_ID, + mediaButtonReceiverComponentName, + mediaButtonReceiverPendingIntent ) val mediaSessionCallback = MediaSessionCallback(this) mediaSession?.setCallback(mediaSessionCallback) mediaSession?.isActive = true + mediaSession?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent) } inner class MusicBinder : Binder() {