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:
Thomas Guillem 2014-10-18 19:02:23 +02:00 committed by Max Kellermann
parent ef38dbe5bf
commit f37ab5482b
7 changed files with 244 additions and 41 deletions

1
NEWS
View File

@ -4,6 +4,7 @@ ver 0.20.22 (not yet released)
* Android
- now runs as a service
- add button to start/stop MPD
- add option to auto-start on boot
ver 0.20.21 (2018/08/17)
* database

View File

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application android:allowBackup="true"
android:icon="@drawable/icon"
@ -21,7 +22,12 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</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>
</manifest>

View File

@ -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" />

View File

@ -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>

View File

@ -4,4 +4,8 @@
<string name="app_name">MPD</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="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>

41
android/src/Receiver.java Normal file
View File

@ -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);
}
}
}
}

View File

@ -19,120 +19,229 @@
package org.musicpd;
import java.util.LinkedList;
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.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.LinearLayout;
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 mTextView;
private ToggleButton mButton;
private LinearLayout mLayout;
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 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");
final String error = (String) msg.obj;
mTextView.setText("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=" + error);
mButton.setChecked(false);
mButton.setEnabled(false);
mClient.release();
connectClient();
mRunButton.setEnabled(false);
mRunButton.setChecked(false);
mTextStatus.setText((String)msg.obj);
mFirstRun = true;
break;
case MSG_STOPPED:
Log.d(TAG, "onStopped");
if (mButton.isEnabled()) // don't overwrite previous error message
mTextView.setText("Music Player Daemon is not running");
mButton.setEnabled(true);
mButton.setChecked(false);
mRunButton.setEnabled(true);
if (!mFirstRun && Preferences.getBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, false))
mRunButton.setChecked(true);
else
mRunButton.setChecked(false);
mFirstRun = true;
break;
case MSG_STARTED:
Log.d(TAG, "onStarted");
mTextView.setText("Music Player Daemon is running"
+ "\nCAUTION: this version is EXPERIMENTAL!");
mButton.setChecked(true);
mRunButton.setChecked(true);
mFirstRun = 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;
}
return true;
}
});
private OnCheckedChangeListener mOnCheckedChangeListener = new OnCheckedChangeListener() {
private final OnCheckedChangeListener mOnRunChangeListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mClient != null) {
if (isChecked)
if (isChecked) {
mClient.start();
else
if (Preferences.getBoolean(Settings.this,
Preferences.KEY_WAKELOCK, false))
mClient.setWakelockEnabled(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);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
mTextView = new TextView(this);
mTextView.setText("");
setContentView(R.layout.settings);
mRunButton = (ToggleButton) findViewById(R.id.run);
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
mButton = new ToggleButton(this);
mButton.setOnCheckedChangeListener(mOnCheckedChangeListener);
mTextStatus = (TextView) findViewById(R.id.status);
mLayout = new LinearLayout(this);
mLayout.setOrientation(LinearLayout.VERTICAL);
mLayout.addView(mButton);
mLayout.addView(mTextView);
mLogListAdapter = new ArrayAdapter<String>(this, R.layout.log_item, mLogListArray);
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);
}
@Override
protected void onStart() {
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() {
mHandler.removeCallbacksAndMessages(null);
removeMessages();
mHandler.sendEmptyMessage(MSG_STOPPED);
}
@Override
public void onStarted() {
mHandler.removeCallbacksAndMessages(null);
removeMessages();
mHandler.sendEmptyMessage(MSG_STARTED);
}
@Override
public void onError(String error) {
mHandler.removeCallbacksAndMessages(null);
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();
}