Merge branch 'v0.20.x'
This commit is contained in:
commit
616abdda26
25
Makefile.am
25
Makefile.am
@ -329,7 +329,8 @@ libjava_a_SOURCES = \
|
|||||||
noinst_LIBRARIES += libandroid.a
|
noinst_LIBRARIES += libandroid.a
|
||||||
libandroid_a_SOURCES = \
|
libandroid_a_SOURCES = \
|
||||||
src/android/Context.cxx src/android/Context.hxx \
|
src/android/Context.cxx src/android/Context.hxx \
|
||||||
src/android/Environment.cxx src/android/Environment.hxx
|
src/android/Environment.cxx src/android/Environment.hxx \
|
||||||
|
src/android/LogListener.cxx src/android/LogListener.hxx
|
||||||
libandroid_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
libandroid_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
||||||
|
|
||||||
noinst_LIBRARIES += libmain.a
|
noinst_LIBRARIES += libmain.a
|
||||||
@ -353,6 +354,7 @@ ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_V
|
|||||||
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
|
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
|
||||||
|
|
||||||
JAVAC = javac
|
JAVAC = javac
|
||||||
|
AIDL = $(ANDROID_BUILD_TOOLS_DIR)/aidl
|
||||||
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
|
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
|
||||||
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
|
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
|
||||||
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
||||||
@ -360,16 +362,26 @@ ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
|||||||
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
|
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
|
||||||
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
|
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
|
||||||
|
|
||||||
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
|
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java Settings.java
|
||||||
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
|
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
|
||||||
|
|
||||||
JAVA_CLASSFILES_DIR = android/build/classes
|
JAVA_CLASSFILES_DIR = android/build/classes
|
||||||
|
|
||||||
|
AIDL_FILES = $(wildcard $(srcdir)/android/src/*.aidl)
|
||||||
|
AIDL_JAVA_FILES = $(patsubst $(srcdir)/android/src/%.aidl,android/build/src/org/musicpd/%.java,$(AIDL_FILES))
|
||||||
|
|
||||||
|
android/build/src/org/musicpd/IMain.java: android/build/src/org/musicpd/IMainCallback.java
|
||||||
|
|
||||||
|
$(AIDL_JAVA_FILES): android/build/src/org/musicpd/%.java: $(srcdir)/android/src/%.aidl
|
||||||
|
@$(MKDIR_P) $(@D)
|
||||||
|
@cp $< $(@D)/
|
||||||
|
$(AIDL) -Iandroid/build/src -oandroid/build/src $(patsubst %.java,%.aidl,$@)
|
||||||
|
|
||||||
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
|
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
|
||||||
@$(MKDIR_P) $(dir $@)
|
@$(MKDIR_P) $(dir $@)
|
||||||
cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
|
cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
|
||||||
|
|
||||||
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png
|
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png android/build/res/drawable/notification_icon.png
|
||||||
@$(MKDIR_P) android/build/gen
|
@$(MKDIR_P) android/build/gen
|
||||||
$(AAPT) package -f -m --auto-add-overlay \
|
$(AAPT) package -f -m --auto-add-overlay \
|
||||||
--custom-package org.musicpd \
|
--custom-package org.musicpd \
|
||||||
@ -382,15 +394,15 @@ android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawabl
|
|||||||
# R.java is generated by aapt, when resources.apk is generated
|
# R.java is generated by aapt, when resources.apk is generated
|
||||||
android/build/gen/org/musicpd/R.java: android/build/resources.apk
|
android/build/gen/org/musicpd/R.java: android/build/resources.apk
|
||||||
|
|
||||||
android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
|
android/build/classes.dex: $(JAVA_SOURCE_PATHS) $(AIDL_JAVA_FILES) android/build/gen/org/musicpd/R.java
|
||||||
@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
|
@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
|
||||||
$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
|
$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
|
||||||
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
|
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
|
||||||
|
-h android/build/include \
|
||||||
-d $(JAVA_CLASSFILES_DIR) $^
|
-d $(JAVA_CLASSFILES_DIR) $^
|
||||||
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
|
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
|
||||||
|
|
||||||
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
|
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
|
||||||
javah -classpath $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) -d $(@D) org.musicpd.Bridge
|
|
||||||
|
|
||||||
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
||||||
|
|
||||||
@ -403,6 +415,9 @@ android/build/res/drawable/icon.png: mpd.svg
|
|||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
rsvg-convert --width=48 --height=48 $< -o $@
|
rsvg-convert --width=48 --height=48 $< -o $@
|
||||||
|
|
||||||
|
android/build/res/drawable/notification_icon.png: android/build/res/drawable/icon.png
|
||||||
|
convert $< -colorspace Gray -gamma 2.2 $@
|
||||||
|
|
||||||
.DELETE_ON_ERROR: android/build/unsigned.apk
|
.DELETE_ON_ERROR: android/build/unsigned.apk
|
||||||
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/$(ANDROID_ABI)/libmpd.so
|
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/$(ANDROID_ABI)/libmpd.so
|
||||||
cp android/build/resources.apk $@
|
cp android/build/resources.apk $@
|
||||||
|
8
NEWS
8
NEWS
@ -44,6 +44,14 @@ ver 0.21 (not yet released)
|
|||||||
* systemd watchdog support
|
* systemd watchdog support
|
||||||
* require GCC 6
|
* require GCC 6
|
||||||
|
|
||||||
|
ver 0.20.22 (not yet released)
|
||||||
|
* storage
|
||||||
|
- curl: URL-encode paths
|
||||||
|
* 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)
|
ver 0.20.21 (2018/08/17)
|
||||||
* database
|
* database
|
||||||
- proxy: add "password" setting
|
- proxy: add "password" setting
|
||||||
|
@ -2,23 +2,32 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="20"
|
android:versionCode="21"
|
||||||
android:versionName="0.20.21">
|
android:versionName="0.20.22">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||||
|
|
||||||
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<activity android:name=".Main"
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
android:label="@string/app_name"
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
android:launchMode="singleInstance">
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
|
<application android:allowBackup="true"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<activity android:name=".Settings"
|
||||||
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<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>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
22
android/res/layout/custom_notification_gb.xml
Normal file
22
android/res/layout/custom_notification_gb.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:padding="10dp" >
|
||||||
|
<ImageView android:id="@+id/image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_marginRight="10dp" />
|
||||||
|
<TextView android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@id/image"
|
||||||
|
style="Custom Notification Title" />
|
||||||
|
<TextView android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@id/image"
|
||||||
|
android:layout_below="@id/title"
|
||||||
|
style="Custom Notification Text" />
|
||||||
|
</RelativeLayout>
|
5
android/res/layout/log_item.xml
Normal file
5
android/res/layout/log_item.xml
Normal 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" />
|
37
android/res/layout/settings.xml
Normal file
37
android/res/layout/settings.xml
Normal 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>
|
@ -2,4 +2,10 @@
|
|||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<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_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>
|
||||||
|
@ -25,6 +25,12 @@ import android.content.Context;
|
|||||||
* Bridge to native code.
|
* Bridge to native code.
|
||||||
*/
|
*/
|
||||||
public class Bridge {
|
public class Bridge {
|
||||||
public static native void run(Context context);
|
|
||||||
|
/* 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 shutdown();
|
||||||
}
|
}
|
||||||
|
12
android/src/IMain.aidl
Normal file
12
android/src/IMain.aidl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package org.musicpd;
|
||||||
|
import org.musicpd.IMainCallback;
|
||||||
|
|
||||||
|
interface IMain
|
||||||
|
{
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void setWakelockEnabled(boolean enabled);
|
||||||
|
boolean isRunning();
|
||||||
|
void registerCallback(IMainCallback cb);
|
||||||
|
void unregisterCallback(IMainCallback cb);
|
||||||
|
}
|
9
android/src/IMainCallback.aidl
Normal file
9
android/src/IMainCallback.aidl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package org.musicpd;
|
||||||
|
|
||||||
|
interface IMainCallback
|
||||||
|
{
|
||||||
|
void onStarted();
|
||||||
|
void onStopped();
|
||||||
|
void onError(String error);
|
||||||
|
void onLog(int priority, String msg);
|
||||||
|
}
|
@ -19,57 +19,406 @@
|
|||||||
|
|
||||||
package org.musicpd;
|
package org.musicpd;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.annotation.TargetApi;
|
||||||
import android.os.Bundle;
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.PowerManager;
|
||||||
import android.widget.TextView;
|
import android.os.RemoteCallbackList;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
public class Main extends Activity implements Runnable {
|
public class Main extends Service implements Runnable {
|
||||||
private static final String TAG = "MPD";
|
private static final String TAG = "Main";
|
||||||
|
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;
|
||||||
|
|
||||||
Thread thread;
|
private static final int MSG_SEND_STATUS = 0;
|
||||||
|
private static final int MSG_SEND_LOG = 1;
|
||||||
|
|
||||||
TextView textView;
|
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 PowerManager.WakeLock mWakelock = null;
|
||||||
|
|
||||||
final Handler quitHandler = new Handler() {
|
static class MainStub extends IMain.Stub {
|
||||||
public void handleMessage(Message msg) {
|
private Main mService;
|
||||||
textView.setText("Music Player Daemon has quit");
|
MainStub(Main service) {
|
||||||
|
mService = service;
|
||||||
|
}
|
||||||
|
public void start() {
|
||||||
|
mService.start();
|
||||||
|
}
|
||||||
|
public void stop() {
|
||||||
|
mService.stop();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: what now? restart?
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
|
||||||
|
private Notification buildNotificationGB(int title, int text, int icon, PendingIntent contentIntent) {
|
||||||
|
final Notification notification = new Notification();
|
||||||
|
notification.icon = R.drawable.icon;
|
||||||
|
notification.contentView = new RemoteViews(getPackageName(), R.layout.custom_notification_gb);
|
||||||
|
notification.contentView.setImageViewResource(R.id.image, icon);
|
||||||
|
notification.contentView.setTextViewText(R.id.title, getText(title));
|
||||||
|
notification.contentView.setTextViewText(R.id.text, getText(text));
|
||||||
|
notification.contentIntent = contentIntent;
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
private Notification buildNotificationHC(int title, int text, int icon, PendingIntent contentIntent) {
|
||||||
|
return new Notification.Builder(this)
|
||||||
|
.setContentTitle(getText(title))
|
||||||
|
.setContentText(getText(text))
|
||||||
|
.setSmallIcon(icon)
|
||||||
|
.setContentIntent(contentIntent)
|
||||||
|
.getNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
private Notification buildNotificationJB(int title, int text, int icon, PendingIntent contentIntent) {
|
||||||
|
return new Notification.Builder(this)
|
||||||
|
.setContentTitle(getText(title))
|
||||||
|
.setContentText(getText(text))
|
||||||
|
.setSmallIcon(icon)
|
||||||
|
.setContentIntent(contentIntent)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start() {
|
||||||
|
if (mThread != null)
|
||||||
|
return;
|
||||||
|
mThread = new Thread(this);
|
||||||
|
mThread.start();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Notification notification;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
notification = buildNotificationJB(
|
||||||
|
R.string.notification_title_mpd_running,
|
||||||
|
R.string.notification_text_mpd_running,
|
||||||
|
R.drawable.notification_icon,
|
||||||
|
contentIntent);
|
||||||
|
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
notification = buildNotificationHC(
|
||||||
|
R.string.notification_title_mpd_running,
|
||||||
|
R.string.notification_text_mpd_running,
|
||||||
|
R.drawable.notification_icon,
|
||||||
|
contentIntent);
|
||||||
|
else
|
||||||
|
notification = buildNotificationGB(
|
||||||
|
R.string.notification_title_mpd_running,
|
||||||
|
R.string.notification_text_mpd_running,
|
||||||
|
R.drawable.notification_icon,
|
||||||
|
contentIntent);
|
||||||
|
|
||||||
|
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 setWakelockEnabled(boolean enabled) {
|
||||||
|
if (enabled && mWakelock == null) {
|
||||||
|
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
|
||||||
|
mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override protected void onCreate(Bundle savedInstanceState) {
|
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (!Loader.loaded) {
|
@Override
|
||||||
TextView tv = new TextView(this);
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
tv.setText("Failed to load the native MPD libary.\n" +
|
synchronized (this) {
|
||||||
"Report this problem to us, and include the following information:\n" +
|
mIMain = IMain.Stub.asInterface(service);
|
||||||
"SUPPORTED_ABIS=" + String.join(", ", Build.SUPPORTED_ABIS) + "\n" +
|
try {
|
||||||
"PRODUCT=" + Build.PRODUCT + "\n" +
|
if (mCallback != null)
|
||||||
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
mIMain.registerCallback(mICallback);
|
||||||
"error=" + Loader.error);
|
} catch (RemoteException e) {
|
||||||
setContentView(tv);
|
if (mCallback != null)
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread == null || !thread.isAlive()) {
|
public boolean start() {
|
||||||
thread = new Thread(this, "NativeMain");
|
synchronized (this) {
|
||||||
thread.start();
|
if (mIMain != null) {
|
||||||
|
try {
|
||||||
|
mIMain.start();
|
||||||
|
return true;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textView = new TextView(this);
|
public boolean stop() {
|
||||||
textView.setText("Music Player Daemon is running"
|
synchronized (this) {
|
||||||
+ "\nCAUTION: this version is EXPERIMENTAL!");
|
if (mIMain != null) {
|
||||||
setContentView(textView);
|
try {
|
||||||
|
mIMain.stop();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void run() {
|
/*
|
||||||
Bridge.run(this);
|
* start Main service without any callback
|
||||||
quitHandler.sendMessage(quitHandler.obtainMessage());
|
*/
|
||||||
|
public static void start(Context context, boolean wakelock) {
|
||||||
|
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
android/src/Receiver.java
Normal file
41
android/src/Receiver.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
254
android/src/Settings.java
Normal file
254
android/src/Settings.java
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 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 java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
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.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 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;
|
||||||
|
break;
|
||||||
|
case MSG_STARTED:
|
||||||
|
Log.d(TAG, "onStarted");
|
||||||
|
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 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);
|
||||||
|
} 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) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,8 @@
|
|||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
#include "android/LogListener.hxx"
|
||||||
|
#include "Main.hxx"
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ToAndroidLogLevel(LogLevel log_level) noexcept
|
ToAndroidLogLevel(LogLevel log_level) noexcept
|
||||||
@ -179,6 +181,9 @@ Log(const Domain &domain, LogLevel level, const char *msg) noexcept
|
|||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||||
"%s: %s", domain.GetName(), msg);
|
"%s: %s", domain.GetName(), msg);
|
||||||
|
if (logListener != nullptr)
|
||||||
|
logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level),
|
||||||
|
"%s: %s", domain.GetName(), msg);
|
||||||
#else
|
#else
|
||||||
|
|
||||||
if (level < log_threshold)
|
if (level < log_threshold)
|
||||||
|
@ -95,6 +95,7 @@
|
|||||||
#include "java/File.hxx"
|
#include "java/File.hxx"
|
||||||
#include "android/Environment.hxx"
|
#include "android/Environment.hxx"
|
||||||
#include "android/Context.hxx"
|
#include "android/Context.hxx"
|
||||||
|
#include "android/LogListener.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "org_musicpd_Bridge.h"
|
#include "org_musicpd_Bridge.h"
|
||||||
#endif
|
#endif
|
||||||
@ -128,6 +129,7 @@ static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
|||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
Context *context;
|
Context *context;
|
||||||
|
LogListener *logListener;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Instance *instance;
|
Instance *instance;
|
||||||
@ -723,16 +725,19 @@ mpd_main_after_fork(const ConfigData &raw_config, const Config &config)
|
|||||||
|
|
||||||
gcc_visibility_default
|
gcc_visibility_default
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context)
|
Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logListener)
|
||||||
{
|
{
|
||||||
Java::Init(env);
|
Java::Init(env);
|
||||||
Java::File::Initialise(env);
|
Java::File::Initialise(env);
|
||||||
Environment::Initialise(env);
|
Environment::Initialise(env);
|
||||||
|
|
||||||
context = new Context(env, _context);
|
context = new Context(env, _context);
|
||||||
|
if (_logListener != nullptr)
|
||||||
|
logListener = new LogListener(env, _logListener);
|
||||||
|
|
||||||
mpd_main(0, nullptr);
|
mpd_main(0, nullptr);
|
||||||
|
|
||||||
|
delete logListener;
|
||||||
delete context;
|
delete context;
|
||||||
Environment::Deinitialise(env);
|
Environment::Deinitialise(env);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,10 @@ class Context;
|
|||||||
struct Instance;
|
struct Instance;
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
|
#include "android/LogListener.hxx"
|
||||||
|
|
||||||
extern Context *context;
|
extern Context *context;
|
||||||
|
extern LogListener *logListener;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern Instance *instance;
|
extern Instance *instance;
|
||||||
|
46
src/android/LogListener.cxx
Normal file
46
src/android/LogListener.cxx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "LogListener.hxx"
|
||||||
|
#include "java/Class.hxx"
|
||||||
|
#include "java/String.hxx"
|
||||||
|
#include "util/AllocatedString.hxx"
|
||||||
|
#include "util/FormatString.hxx"
|
||||||
|
|
||||||
|
void
|
||||||
|
LogListener::OnLog(JNIEnv *env, int priority, const char *fmt, ...) const
|
||||||
|
{
|
||||||
|
assert(env != nullptr);
|
||||||
|
|
||||||
|
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||||
|
|
||||||
|
jmethodID method = env->GetMethodID(cls, "onLog",
|
||||||
|
"(ILjava/lang/String;)V");
|
||||||
|
|
||||||
|
assert(method);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
const auto log = FormatStringV(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
env->CallVoidMethod(Get(), method, priority,
|
||||||
|
Java::String(env, log.c_str()).Get());
|
||||||
|
}
|
32
src/android/LogListener.hxx
Normal file
32
src/android/LogListener.hxx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_ANDROID_LOG_LISTENER_HXX
|
||||||
|
#define MPD_ANDROID_LOG_LISTENER_HXX
|
||||||
|
|
||||||
|
#include "java/Object.hxx"
|
||||||
|
|
||||||
|
class LogListener : public Java::Object {
|
||||||
|
public:
|
||||||
|
LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
||||||
|
|
||||||
|
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -36,6 +36,7 @@
|
|||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "util/ChronoUtil.hxx"
|
||||||
|
#include "util/IterableSplitString.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
@ -77,9 +78,18 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
|
|||||||
if (StringIsEmpty(uri_utf8))
|
if (StringIsEmpty(uri_utf8))
|
||||||
return base;
|
return base;
|
||||||
|
|
||||||
// TODO: escape the given URI
|
CurlEasy easy;
|
||||||
|
std::string path_esc;
|
||||||
|
|
||||||
return PathTraitsUTF8::Build(base.c_str(), uri_utf8);
|
for (auto elt: IterableSplitString(uri_utf8, '/')) {
|
||||||
|
char *elt_esc = easy.Escape(elt.data, elt.size);
|
||||||
|
if (!path_esc.empty())
|
||||||
|
path_esc.push_back('/');
|
||||||
|
path_esc += elt_esc;
|
||||||
|
curl_free(elt_esc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
|
Loading…
Reference in New Issue
Block a user