android: migrate app build system to use gradle
Most of the Android specific meson code has been removed and replaced with the grade build system. The new meson build scripts build and move the libmpd.so binaries into the correct location that gradle expects. After than gradle handles building the rest of the Android app. Icons and banners have been updated for the modern app packaging expectations. For reference here was the figma template Google provides that I used to back the png versions for older versions of Android <https://www.figma.com/community/file/1283953738855070149>
This commit is contained in:
21
android/app/src/main/java/org/musicpd/Bridge.java
Normal file
21
android/app/src/main/java/org/musicpd/Bridge.java
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Bridge to native code.
|
||||
*/
|
||||
public class Bridge {
|
||||
|
||||
/* used by jni */
|
||||
public interface LogListener {
|
||||
public void onLog(int priority, String msg);
|
||||
}
|
||||
|
||||
public static native void run(Context context, LogListener logListener);
|
||||
public static native void shutdown();
|
||||
public static native void pause();
|
||||
}
|
||||
23
android/app/src/main/java/org/musicpd/Loader.java
Normal file
23
android/app/src/main/java/org/musicpd/Loader.java
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Loader {
|
||||
private static final String TAG = "MPD";
|
||||
|
||||
public static boolean loaded = false;
|
||||
public static String error;
|
||||
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("mpd");
|
||||
loaded = true;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
Log.e(TAG, e.getMessage());
|
||||
error = e.getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
462
android/app/src/main/java/org/musicpd/Main.java
Normal file
462
android/app/src/main/java/org/musicpd/Main.java
Normal file
@@ -0,0 +1,462 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.core.app.ServiceCompat;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class Main extends Service implements Runnable {
|
||||
private static final String TAG = "Main";
|
||||
private static final String WAKELOCK_TAG = "mpd:wakelockmain";
|
||||
private static final String REMOTE_ERROR = "MPD process was killed";
|
||||
private static final int MAIN_STATUS_ERROR = -1;
|
||||
private static final int MAIN_STATUS_STOPPED = 0;
|
||||
private static final int MAIN_STATUS_STARTED = 1;
|
||||
|
||||
private static final int MSG_SEND_STATUS = 0;
|
||||
private static final int MSG_SEND_LOG = 1;
|
||||
|
||||
private Thread mThread = null;
|
||||
private int mStatus = MAIN_STATUS_STOPPED;
|
||||
private boolean mAbort = false;
|
||||
private String mError = null;
|
||||
private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>();
|
||||
private final IBinder mBinder = new MainStub(this);
|
||||
private boolean mPauseOnHeadphonesDisconnect = false;
|
||||
private PowerManager.WakeLock mWakelock = null;
|
||||
|
||||
static class MainStub extends IMain.Stub {
|
||||
private Main mService;
|
||||
MainStub(Main service) {
|
||||
mService = service;
|
||||
}
|
||||
public void start() {
|
||||
mService.start();
|
||||
}
|
||||
public void stop() {
|
||||
mService.stop();
|
||||
}
|
||||
public void setPauseOnHeadphonesDisconnect(boolean enabled) {
|
||||
mService.setPauseOnHeadphonesDisconnect(enabled);
|
||||
}
|
||||
public void setWakelockEnabled(boolean enabled) {
|
||||
mService.setWakelockEnabled(enabled);
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return mService.isRunning();
|
||||
}
|
||||
public void registerCallback(IMainCallback cb) {
|
||||
mService.registerCallback(cb);
|
||||
}
|
||||
public void unregisterCallback(IMainCallback cb) {
|
||||
mService.unregisterCallback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void sendMessage(int what, int arg1, int arg2, Object obj) {
|
||||
int i = mCallbacks.beginBroadcast();
|
||||
while (i > 0) {
|
||||
i--;
|
||||
final IMainCallback cb = mCallbacks.getBroadcastItem(i);
|
||||
try {
|
||||
switch (what) {
|
||||
case MSG_SEND_STATUS:
|
||||
switch (arg1) {
|
||||
case MAIN_STATUS_ERROR:
|
||||
cb.onError((String)obj);
|
||||
break;
|
||||
case MAIN_STATUS_STOPPED:
|
||||
cb.onStopped();
|
||||
break;
|
||||
case MAIN_STATUS_STARTED:
|
||||
cb.onStarted();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MSG_SEND_LOG:
|
||||
cb.onLog(arg1, (String) obj);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
mCallbacks.finishBroadcast();
|
||||
}
|
||||
|
||||
private Bridge.LogListener mLogListener = new Bridge.LogListener() {
|
||||
@Override
|
||||
public void onLog(int priority, String msg) {
|
||||
sendMessage(MSG_SEND_LOG, priority, 0, msg);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
start();
|
||||
if (intent != null && intent.getBooleanExtra("wakelock", false))
|
||||
setWakelockEnabled(true);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!Loader.loaded) {
|
||||
final String error = "Failed to load the native MPD libary.\n" +
|
||||
"Report this problem to us, and include the following information:\n" +
|
||||
"SUPPORTED_ABIS=" + String.join(", ", Build.SUPPORTED_ABIS) + "\n" +
|
||||
"PRODUCT=" + Build.PRODUCT + "\n" +
|
||||
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
||||
"error=" + Loader.error;
|
||||
setStatus(MAIN_STATUS_ERROR, error);
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (mAbort)
|
||||
return;
|
||||
setStatus(MAIN_STATUS_STARTED, null);
|
||||
}
|
||||
Bridge.run(this, mLogListener);
|
||||
setStatus(MAIN_STATUS_STOPPED, null);
|
||||
}
|
||||
|
||||
private synchronized void setStatus(int status, String error) {
|
||||
mStatus = status;
|
||||
mError = error;
|
||||
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||
}
|
||||
|
||||
private Notification.Builder createNotificationBuilderWithChannel() {
|
||||
final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (notificationManager == null)
|
||||
return null;
|
||||
|
||||
final String id = "org.musicpd";
|
||||
final String name = "MPD service";
|
||||
final int importance = 3; /* NotificationManager.IMPORTANCE_DEFAULT */
|
||||
|
||||
try {
|
||||
Class<?> ncClass = Class.forName("android.app.NotificationChannel");
|
||||
Constructor<?> ncCtor = ncClass.getConstructor(String.class, CharSequence.class, int.class);
|
||||
Object nc = ncCtor.newInstance(id, name, importance);
|
||||
|
||||
Method nmCreateNotificationChannelMethod =
|
||||
NotificationManager.class.getMethod("createNotificationChannel", ncClass);
|
||||
nmCreateNotificationChannelMethod.invoke(notificationManager, nc);
|
||||
|
||||
Constructor nbCtor = Notification.Builder.class.getConstructor(Context.class, String.class);
|
||||
return (Notification.Builder) nbCtor.newInstance(this, id);
|
||||
} catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, "error creating the NotificationChannel", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if (mThread != null)
|
||||
return;
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
||||
registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mPauseOnHeadphonesDisconnect)
|
||||
return;
|
||||
if (intent.getAction() == AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||
pause();
|
||||
}
|
||||
}, filter);
|
||||
|
||||
final Intent mainIntent = new Intent(this, Settings.class);
|
||||
mainIntent.setAction("android.intent.action.MAIN");
|
||||
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
||||
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Notification.Builder nBuilder;
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Build.VERSION_CODES.O */)
|
||||
{
|
||||
nBuilder = createNotificationBuilderWithChannel();
|
||||
if (nBuilder == null)
|
||||
return;
|
||||
}
|
||||
else
|
||||
nBuilder = new Notification.Builder(this);
|
||||
|
||||
Notification notification = nBuilder.setContentTitle(getText(R.string.notification_title_mpd_running))
|
||||
.setContentText(getText(R.string.notification_text_mpd_running))
|
||||
.setSmallIcon(R.drawable.notification_icon)
|
||||
.setContentIntent(contentIntent)
|
||||
.build();
|
||||
|
||||
mThread = new Thread(this);
|
||||
mThread.start();
|
||||
|
||||
startForeground(R.string.notification_title_mpd_running, notification);
|
||||
startService(new Intent(this, Main.class));
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
if (mThread != null) {
|
||||
if (mThread.isAlive()) {
|
||||
synchronized (this) {
|
||||
if (mStatus == MAIN_STATUS_STARTED)
|
||||
Bridge.shutdown();
|
||||
else
|
||||
mAbort = true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
mThread.join();
|
||||
mThread = null;
|
||||
mAbort = false;
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
setWakelockEnabled(false);
|
||||
stopForeground(true);
|
||||
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) {
|
||||
if (enabled && mWakelock == null) {
|
||||
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
|
||||
mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
|
||||
mWakelock.acquire();
|
||||
Log.d(TAG, "Wakelock acquired");
|
||||
} else if (!enabled && mWakelock != null) {
|
||||
mWakelock.release();
|
||||
mWakelock = null;
|
||||
Log.d(TAG, "Wakelock released");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRunning() {
|
||||
return mThread != null && mThread.isAlive();
|
||||
}
|
||||
|
||||
private void registerCallback(IMainCallback cb) {
|
||||
if (cb != null) {
|
||||
mCallbacks.register(cb);
|
||||
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterCallback(IMainCallback cb) {
|
||||
if (cb != null) {
|
||||
mCallbacks.unregister(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Client that bind the Main Service in order to send commands and receive callback
|
||||
*/
|
||||
public static class Client {
|
||||
|
||||
public interface Callback {
|
||||
public void onStarted();
|
||||
public void onStopped();
|
||||
public void onError(String error);
|
||||
public void onLog(int priority, String msg);
|
||||
}
|
||||
|
||||
private boolean mBound = false;
|
||||
private final Context mContext;
|
||||
private Callback mCallback;
|
||||
private IMain mIMain = null;
|
||||
|
||||
private final IMainCallback.Stub mICallback = new IMainCallback.Stub() {
|
||||
|
||||
@Override
|
||||
public void onStopped() throws RemoteException {
|
||||
mCallback.onStopped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStarted() throws RemoteException {
|
||||
mCallback.onStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) throws RemoteException {
|
||||
mCallback.onError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLog(int priority, String msg) throws RemoteException {
|
||||
mCallback.onLog(priority, msg);
|
||||
}
|
||||
};
|
||||
|
||||
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
synchronized (this) {
|
||||
mIMain = IMain.Stub.asInterface(service);
|
||||
try {
|
||||
if (mCallback != null)
|
||||
mIMain.registerCallback(mICallback);
|
||||
} catch (RemoteException e) {
|
||||
if (mCallback != null)
|
||||
mCallback.onError(REMOTE_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (mCallback != null)
|
||||
mCallback.onError(REMOTE_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
public Client(Context context, Callback cb) throws IllegalArgumentException {
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("Context can't be null");
|
||||
mContext = context;
|
||||
mCallback = cb;
|
||||
mBound = mContext.bindService(new Intent(mContext, Main.class), mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.start();
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean stop() {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.stop();
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.setWakelockEnabled(enabled);
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
return mIMain.isRunning();
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (mBound) {
|
||||
synchronized (this) {
|
||||
if (mIMain != null && mICallback != null) {
|
||||
try {
|
||||
if (mCallback != null)
|
||||
mIMain.unregisterCallback(mICallback);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
mBound = false;
|
||||
mContext.unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* start Main service without any callback
|
||||
*/
|
||||
public static void start(Context context, boolean wakelock) {
|
||||
Intent intent = new Intent(context, Main.class)
|
||||
.putExtra("wakelock", wakelock);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
/* in Android 8+, we need to use this method
|
||||
or else we'll get "IllegalStateException:
|
||||
app is in background" */
|
||||
context.startForegroundService(intent);
|
||||
else
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
26
android/app/src/main/java/org/musicpd/Receiver.java
Normal file
26
android/app/src/main/java/org/musicpd/Receiver.java
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class Receiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("Receiver", "onReceive: " + intent);
|
||||
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
|
||||
if (Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_RUN_ON_BOOT,
|
||||
false)) {
|
||||
final boolean wakelock =
|
||||
Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_WAKELOCK, false);
|
||||
Main.start(context, wakelock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
292
android/app/src/main/java/org/musicpd/Settings.java
Normal file
292
android/app/src/main/java/org/musicpd/Settings.java
Normal file
@@ -0,0 +1,292 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright The Music Player Daemon Project
|
||||
|
||||
package org.musicpd;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
public class Settings extends Activity {
|
||||
private static final String TAG = "Settings";
|
||||
private Main.Client mClient;
|
||||
private TextView mTextStatus;
|
||||
private ToggleButton mRunButton;
|
||||
private boolean mFirstRun;
|
||||
private LinkedList<String> mLogListArray = new LinkedList<String>();
|
||||
private ListView mLogListView;
|
||||
private ArrayAdapter<String> mLogListAdapter;
|
||||
|
||||
private static final int MAX_LOGS = 500;
|
||||
|
||||
private static final int MSG_ERROR = 0;
|
||||
private static final int MSG_STOPPED = 1;
|
||||
private static final int MSG_STARTED = 2;
|
||||
private static final int MSG_LOG = 3;
|
||||
|
||||
public static class Preferences {
|
||||
public static final String KEY_RUN_ON_BOOT ="run_on_boot";
|
||||
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) {
|
||||
return context.getSharedPreferences(TAG, MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void putBoolean(Context context, String key, boolean value) {
|
||||
final SharedPreferences prefs = get(context);
|
||||
|
||||
if (prefs == null)
|
||||
return;
|
||||
final Editor editor = prefs.edit();
|
||||
editor.putBoolean(key, value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static boolean getBoolean(Context context, String key, boolean defValue) {
|
||||
final SharedPreferences prefs = get(context);
|
||||
|
||||
return prefs != null ? prefs.getBoolean(key, defValue) : defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private Handler mHandler = new Handler(new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_ERROR:
|
||||
Log.d(TAG, "onError");
|
||||
|
||||
mClient.release();
|
||||
connectClient();
|
||||
|
||||
mRunButton.setEnabled(false);
|
||||
mRunButton.setChecked(false);
|
||||
|
||||
mTextStatus.setText((String)msg.obj);
|
||||
mFirstRun = true;
|
||||
break;
|
||||
case MSG_STOPPED:
|
||||
Log.d(TAG, "onStopped");
|
||||
mRunButton.setEnabled(true);
|
||||
if (!mFirstRun && Preferences.getBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||
mRunButton.setChecked(true);
|
||||
else
|
||||
mRunButton.setChecked(false);
|
||||
mFirstRun = true;
|
||||
mTextStatus.setText("");
|
||||
break;
|
||||
case MSG_STARTED:
|
||||
Log.d(TAG, "onStarted");
|
||||
mRunButton.setChecked(true);
|
||||
mFirstRun = true;
|
||||
mTextStatus.setText("MPD service started");
|
||||
break;
|
||||
case MSG_LOG:
|
||||
if (mLogListArray.size() > MAX_LOGS)
|
||||
mLogListArray.remove(0);
|
||||
String priority;
|
||||
switch (msg.arg1) {
|
||||
case Log.DEBUG:
|
||||
priority = "D";
|
||||
break;
|
||||
case Log.ERROR:
|
||||
priority = "E";
|
||||
break;
|
||||
case Log.INFO:
|
||||
priority = "I";
|
||||
break;
|
||||
case Log.VERBOSE:
|
||||
priority = "V";
|
||||
break;
|
||||
case Log.WARN:
|
||||
priority = "W";
|
||||
break;
|
||||
default:
|
||||
priority = "";
|
||||
}
|
||||
mLogListArray.add(priority + "/ " + (String)msg.obj);
|
||||
mLogListAdapter.notifyDataSetChanged();
|
||||
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
private final OnCheckedChangeListener mOnRunChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (mClient != null) {
|
||||
if (isChecked) {
|
||||
mClient.start();
|
||||
if (Preferences.getBoolean(Settings.this,
|
||||
Preferences.KEY_WAKELOCK, false))
|
||||
mClient.setWakelockEnabled(true);
|
||||
if (Preferences.getBoolean(Settings.this,
|
||||
Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
|
||||
mClient.setPauseOnHeadphonesDisconnect(true);
|
||||
} else {
|
||||
mClient.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final OnCheckedChangeListener mOnRunOnBootChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Preferences.putBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, isChecked);
|
||||
if (isChecked && mClient != null && !mRunButton.isChecked())
|
||||
mRunButton.setChecked(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnCheckedChangeListener mOnWakelockChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Preferences.putBoolean(Settings.this, Preferences.KEY_WAKELOCK, isChecked);
|
||||
if (mClient != null && mClient.isRunning())
|
||||
mClient.setWakelockEnabled(isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
/* TODO: this sure is the wrong place to request
|
||||
permissions - it will cause MPD to quit
|
||||
immediately; we should request permissions when we
|
||||
need them, but implementing that is complicated, so
|
||||
for now, we do it here to give users a quick
|
||||
solution for the problem */
|
||||
requestAllPermissions();
|
||||
|
||||
setContentView(R.layout.settings);
|
||||
mRunButton = (ToggleButton) findViewById(R.id.run);
|
||||
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
|
||||
|
||||
mTextStatus = (TextView) findViewById(R.id.status);
|
||||
|
||||
mLogListAdapter = new ArrayAdapter<String>(this, R.layout.log_item, mLogListArray);
|
||||
|
||||
mLogListView = (ListView) findViewById(R.id.log_list);
|
||||
mLogListView.setAdapter(mLogListAdapter);
|
||||
mLogListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
|
||||
|
||||
CheckBox checkbox = (CheckBox) findViewById(R.id.run_on_boot);
|
||||
checkbox.setOnCheckedChangeListener(mOnRunOnBootChangeListener);
|
||||
if (Preferences.getBoolean(this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||
checkbox.setChecked(true);
|
||||
|
||||
checkbox = (CheckBox) findViewById(R.id.wakelock);
|
||||
checkbox.setOnCheckedChangeListener(mOnWakelockChangeListener);
|
||||
if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false))
|
||||
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);
|
||||
}
|
||||
|
||||
private void checkRequestPermission(String permission) {
|
||||
if (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED)
|
||||
return;
|
||||
|
||||
try {
|
||||
this.requestPermissions(new String[]{permission}, 0);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "requestPermissions(" + permission + ") failed",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
private void requestAllPermissions() {
|
||||
if (android.os.Build.VERSION.SDK_INT < 23)
|
||||
/* we don't need to request permissions on
|
||||
this old Android version */
|
||||
return;
|
||||
|
||||
/* starting with Android 6.0, we need to explicitly
|
||||
request all permissions before using them;
|
||||
mentioning them in the manifest is not enough */
|
||||
|
||||
checkRequestPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
private void connectClient() {
|
||||
mClient = new Main.Client(this, new Main.Client.Callback() {
|
||||
|
||||
private void removeMessages() {
|
||||
/* don't remove log messages */
|
||||
mHandler.removeMessages(MSG_STOPPED);
|
||||
mHandler.removeMessages(MSG_STARTED);
|
||||
mHandler.removeMessages(MSG_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped() {
|
||||
removeMessages();
|
||||
mHandler.sendEmptyMessage(MSG_STOPPED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStarted() {
|
||||
removeMessages();
|
||||
mHandler.sendEmptyMessage(MSG_STARTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
removeMessages();
|
||||
mHandler.sendMessage(Message.obtain(mHandler, MSG_ERROR, error));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLog(int priority, String msg) {
|
||||
mHandler.sendMessage(Message.obtain(mHandler, MSG_LOG, priority, 0, msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
mFirstRun = false;
|
||||
connectClient();
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mClient.release();
|
||||
mClient = null;
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user