android: improve Settings UI and run mpd on boot
add 2 preferences to: - enable Wakelock when MPD is running (prevent suspend) - run MPD on boot and display MPD logs
This commit is contained in:
parent
ef38dbe5bf
commit
f37ab5482b
1
NEWS
1
NEWS
|
@ -4,6 +4,7 @@ ver 0.20.22 (not yet released)
|
||||||
* Android
|
* Android
|
||||||
- now runs as a service
|
- now runs as a service
|
||||||
- add button to start/stop MPD
|
- add button to start/stop MPD
|
||||||
|
- add option to auto-start on boot
|
||||||
|
|
||||||
ver 0.20.21 (2018/08/17)
|
ver 0.20.21 (2018/08/17)
|
||||||
* database
|
* database
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application android:allowBackup="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
|
@ -21,7 +22,12 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<service android:name=".Main" />
|
<receiver android:name=".Receiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<service android:name=".Main" android:process=":main"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:typeface="monospace" />
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/run"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textOn="@string/toggle_button_run_on"
|
||||||
|
android:textOff="@string/toggle_button_run_off" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/run_on_boot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/checkbox_run_on_boot" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/wakelock"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/checkbox_wakelock" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/log_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="10dip" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -4,4 +4,8 @@
|
||||||
<string name="app_name">MPD</string>
|
<string name="app_name">MPD</string>
|
||||||
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
|
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
|
||||||
<string name="notification_text_mpd_running">Touch for MPD options.</string>
|
<string name="notification_text_mpd_running">Touch for MPD options.</string>
|
||||||
|
<string name="toggle_button_run_on">MPD is 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_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program 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 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,120 +19,229 @@
|
||||||
|
|
||||||
package org.musicpd;
|
package org.musicpd;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Build;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
public class Settings extends Activity {
|
public class Settings extends Activity {
|
||||||
private static final String TAG = "Settings";
|
private static final String TAG = "Settings";
|
||||||
private Main.Client mClient;
|
private Main.Client mClient;
|
||||||
private TextView mTextView;
|
private TextView mTextStatus;
|
||||||
private ToggleButton mButton;
|
private ToggleButton mRunButton;
|
||||||
private LinearLayout mLayout;
|
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_ERROR = 0;
|
||||||
private static final int MSG_STOPPED = 1;
|
private static final int MSG_STOPPED = 1;
|
||||||
private static final int MSG_STARTED = 2;
|
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 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() {
|
private Handler mHandler = new Handler(new Handler.Callback() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handleMessage(Message msg) {
|
public boolean handleMessage(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_ERROR:
|
case MSG_ERROR:
|
||||||
Log.d(TAG, "onError");
|
Log.d(TAG, "onError");
|
||||||
final String error = (String) msg.obj;
|
|
||||||
mTextView.setText("Failed to load the native MPD libary.\n" +
|
mClient.release();
|
||||||
"Report this problem to us, and include the following information:\n" +
|
connectClient();
|
||||||
"SUPPORTED_ABIS=" + String.join(", ", Build.SUPPORTED_ABIS) + "\n" +
|
|
||||||
"PRODUCT=" + Build.PRODUCT + "\n" +
|
mRunButton.setEnabled(false);
|
||||||
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
mRunButton.setChecked(false);
|
||||||
"error=" + error);
|
|
||||||
mButton.setChecked(false);
|
mTextStatus.setText((String)msg.obj);
|
||||||
mButton.setEnabled(false);
|
mFirstRun = true;
|
||||||
break;
|
break;
|
||||||
case MSG_STOPPED:
|
case MSG_STOPPED:
|
||||||
Log.d(TAG, "onStopped");
|
Log.d(TAG, "onStopped");
|
||||||
if (mButton.isEnabled()) // don't overwrite previous error message
|
mRunButton.setEnabled(true);
|
||||||
mTextView.setText("Music Player Daemon is not running");
|
if (!mFirstRun && Preferences.getBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||||
mButton.setEnabled(true);
|
mRunButton.setChecked(true);
|
||||||
mButton.setChecked(false);
|
else
|
||||||
|
mRunButton.setChecked(false);
|
||||||
|
mFirstRun = true;
|
||||||
break;
|
break;
|
||||||
case MSG_STARTED:
|
case MSG_STARTED:
|
||||||
Log.d(TAG, "onStarted");
|
Log.d(TAG, "onStarted");
|
||||||
mTextView.setText("Music Player Daemon is running"
|
mRunButton.setChecked(true);
|
||||||
+ "\nCAUTION: this version is EXPERIMENTAL!");
|
mFirstRun = true;
|
||||||
mButton.setChecked(true);
|
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private OnCheckedChangeListener mOnCheckedChangeListener = new OnCheckedChangeListener() {
|
private final OnCheckedChangeListener mOnRunChangeListener = new OnCheckedChangeListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
if (mClient != null) {
|
if (mClient != null) {
|
||||||
if (isChecked)
|
if (isChecked) {
|
||||||
mClient.start();
|
mClient.start();
|
||||||
else
|
if (Preferences.getBoolean(Settings.this,
|
||||||
|
Preferences.KEY_WAKELOCK, false))
|
||||||
|
mClient.setWakelockEnabled(true);
|
||||||
|
} else {
|
||||||
mClient.stop();
|
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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
mTextView = new TextView(this);
|
setContentView(R.layout.settings);
|
||||||
mTextView.setText("");
|
mRunButton = (ToggleButton) findViewById(R.id.run);
|
||||||
|
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
|
||||||
|
|
||||||
mButton = new ToggleButton(this);
|
mTextStatus = (TextView) findViewById(R.id.status);
|
||||||
mButton.setOnCheckedChangeListener(mOnCheckedChangeListener);
|
|
||||||
|
|
||||||
mLayout = new LinearLayout(this);
|
mLogListAdapter = new ArrayAdapter<String>(this, R.layout.log_item, mLogListArray);
|
||||||
mLayout.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
mLayout.addView(mButton);
|
|
||||||
mLayout.addView(mTextView);
|
|
||||||
|
|
||||||
setContentView(mLayout);
|
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);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void connectClient() {
|
||||||
protected void onStart() {
|
|
||||||
mClient = new Main.Client(this, new Main.Client.Callback() {
|
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
|
@Override
|
||||||
public void onStopped() {
|
public void onStopped() {
|
||||||
mHandler.removeCallbacksAndMessages(null);
|
removeMessages();
|
||||||
mHandler.sendEmptyMessage(MSG_STOPPED);
|
mHandler.sendEmptyMessage(MSG_STOPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStarted() {
|
public void onStarted() {
|
||||||
mHandler.removeCallbacksAndMessages(null);
|
removeMessages();
|
||||||
mHandler.sendEmptyMessage(MSG_STARTED);
|
mHandler.sendEmptyMessage(MSG_STARTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(String error) {
|
public void onError(String error) {
|
||||||
mHandler.removeCallbacksAndMessages(null);
|
removeMessages();
|
||||||
mHandler.sendMessage(Message.obtain(mHandler, MSG_ERROR, error));
|
mHandler.sendMessage(Message.obtain(mHandler, MSG_ERROR, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLog(int priority, String msg) {
|
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();
|
super.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue