Android: add option to pause MPD when headphones disconnect

This commit is contained in:
Sam Bazley 2021-07-26 18:55:39 +01:00
parent d39b11ba5d
commit 57687779be
8 changed files with 97 additions and 0 deletions

View File

@ -17,6 +17,8 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application android:allowBackup="true" <application android:allowBackup="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
@ -41,6 +43,7 @@
<receiver android:name=".Receiver"> <receiver android:name=".Receiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.HEADSET_PLUG" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<service android:name=".Main" android:process=":main"/> <service android:name=".Main" android:process=":main"/>

View File

@ -23,6 +23,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/checkbox_wakelock" /> android:text="@string/checkbox_wakelock" />
<CheckBox
android:id="@+id/pause_on_headphones_disconnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/checkbox_pause_on_headphones_disconnect" />
<TextView <TextView
android:id="@+id/status" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -8,4 +8,5 @@
<string name="toggle_button_run_off">MPD is not running</string> <string name="toggle_button_run_off">MPD is not running</string>
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string> <string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string> <string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
</resources> </resources>

View File

@ -33,4 +33,5 @@ public class Bridge {
public static native void run(Context context, LogListener logListener); public static native void run(Context context, LogListener logListener);
public static native void shutdown(); public static native void shutdown();
public static native void pause();
} }

View File

@ -5,6 +5,7 @@ interface IMain
{ {
void start(); void start();
void stop(); void stop();
void setPauseOnHeadphonesDisconnect(boolean enabled);
void setWakelockEnabled(boolean enabled); void setWakelockEnabled(boolean enabled);
boolean isRunning(); boolean isRunning();
void registerCallback(IMainCallback cb); void registerCallback(IMainCallback cb);

View File

@ -24,9 +24,13 @@ import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothClass;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
@ -55,6 +59,7 @@ public class Main extends Service implements Runnable {
private String mError = null; private String mError = null;
private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>(); private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>();
private final IBinder mBinder = new MainStub(this); private final IBinder mBinder = new MainStub(this);
private boolean mPauseOnHeadphonesDisconnect = false;
private PowerManager.WakeLock mWakelock = null; private PowerManager.WakeLock mWakelock = null;
static class MainStub extends IMain.Stub { static class MainStub extends IMain.Stub {
@ -68,6 +73,9 @@ public class Main extends Service implements Runnable {
public void stop() { public void stop() {
mService.stop(); mService.stop();
} }
public void setPauseOnHeadphonesDisconnect(boolean enabled) {
mService.setPauseOnHeadphonesDisconnect(enabled);
}
public void setWakelockEnabled(boolean enabled) { public void setWakelockEnabled(boolean enabled) {
mService.setWakelockEnabled(enabled); mService.setWakelockEnabled(enabled);
} }
@ -191,6 +199,28 @@ public class Main extends Service implements Runnable {
if (mThread != null) if (mThread != null)
return; return;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_HEADSET_PLUG);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mPauseOnHeadphonesDisconnect) {
return;
}
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
if (intent.hasExtra("state") && intent.getIntExtra("state", 0) == 0)
pause();
} else {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBluetoothClass().hasService(BluetoothClass.Service.AUDIO))
pause();
}
}
}, filter);
final Intent mainIntent = new Intent(this, Settings.class); final Intent mainIntent = new Intent(this, Settings.class);
mainIntent.setAction("android.intent.action.MAIN"); mainIntent.setAction("android.intent.action.MAIN");
mainIntent.addCategory("android.intent.category.LAUNCHER"); mainIntent.addCategory("android.intent.category.LAUNCHER");
@ -241,6 +271,21 @@ public class Main extends Service implements Runnable {
stopSelf(); stopSelf();
} }
private void pause() {
if (mThread != null) {
if (mThread.isAlive()) {
synchronized (this) {
if (mStatus == MAIN_STATUS_STARTED)
Bridge.pause();
}
}
}
}
private void setPauseOnHeadphonesDisconnect(boolean enabled) {
mPauseOnHeadphonesDisconnect = enabled;
}
private void setWakelockEnabled(boolean enabled) { private void setWakelockEnabled(boolean enabled) {
if (enabled && mWakelock == null) { if (enabled && mWakelock == null) {
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
@ -368,6 +413,19 @@ public class Main extends Service implements Runnable {
} }
} }
public boolean setPauseOnHeadphonesDisconnect(boolean enabled) {
synchronized (this) {
if (mIMain != null) {
try {
mIMain.setPauseOnHeadphonesDisconnect(enabled);
return true;
} catch (RemoteException e) {
}
}
return false;
}
}
public boolean setWakelockEnabled(boolean enabled) { public boolean setWakelockEnabled(boolean enabled) {
synchronized (this) { synchronized (this) {
if (mIMain != null) { if (mIMain != null) {

View File

@ -59,6 +59,7 @@ public class Settings extends Activity {
public static class Preferences { public static class Preferences {
public static final String KEY_RUN_ON_BOOT ="run_on_boot"; public static final String KEY_RUN_ON_BOOT ="run_on_boot";
public static final String KEY_WAKELOCK ="wakelock"; public static final String KEY_WAKELOCK ="wakelock";
public static final String KEY_PAUSE_ON_HEADPHONES_DISCONNECT ="pause_on_headphones_disconnect";
public static SharedPreferences get(Context context) { public static SharedPreferences get(Context context) {
return context.getSharedPreferences(TAG, MODE_PRIVATE); return context.getSharedPreferences(TAG, MODE_PRIVATE);
@ -154,6 +155,9 @@ public class Settings extends Activity {
if (Preferences.getBoolean(Settings.this, if (Preferences.getBoolean(Settings.this,
Preferences.KEY_WAKELOCK, false)) Preferences.KEY_WAKELOCK, false))
mClient.setWakelockEnabled(true); mClient.setWakelockEnabled(true);
if (Preferences.getBoolean(Settings.this,
Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
mClient.setPauseOnHeadphonesDisconnect(true);
} else { } else {
mClient.stop(); mClient.stop();
} }
@ -179,6 +183,15 @@ public class Settings extends Activity {
} }
}; };
private final OnCheckedChangeListener mOnPauseOnHeadphonesDisconnectChangeListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Preferences.putBoolean(Settings.this, Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, isChecked);
if (mClient != null && mClient.isRunning())
mClient.setPauseOnHeadphonesDisconnect(isChecked);
}
};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
/* TODO: this sure is the wrong place to request /* TODO: this sure is the wrong place to request
@ -211,6 +224,11 @@ public class Settings extends Activity {
if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false)) if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false))
checkbox.setChecked(true); checkbox.setChecked(true);
checkbox = (CheckBox) findViewById(R.id.pause_on_headphones_disconnect);
checkbox.setOnCheckedChangeListener(mOnPauseOnHeadphonesDisconnectChangeListener);
if (Preferences.getBoolean(this, Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
checkbox.setChecked(true);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }

View File

@ -612,6 +612,15 @@ Java_org_musicpd_Bridge_shutdown(JNIEnv *, jclass)
global_instance->Break(); global_instance->Break();
} }
gcc_visibility_default
JNIEXPORT void JNICALL
Java_org_musicpd_Bridge_pause(JNIEnv *, jclass)
{
if (global_instance != nullptr)
for (auto &partition : global_instance->partitions)
partition.pc.LockSetPause(true);
}
#else #else
static inline void static inline void