Commit 616abdda authored by Max Kellermann's avatar Max Kellermann

Merge branch 'v0.20.x'

parents cc64c715 14d3a7ae
...@@ -329,7 +329,8 @@ libjava_a_SOURCES = \ ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 $@
......
...@@ -44,6 +44,14 @@ ver 0.21 (not yet released) ...@@ -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 @@ ...@@ -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>
<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>
<?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" />
<?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 @@ ...@@ -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; ...@@ -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();
} }
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);
}
package org.musicpd;
interface IMainCallback
{
void onStarted();
void onStopped();
void onError(String error);
void onLog(int priority, String msg);
}
/*
* 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);
}
}
}
}
/*
* 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 @@ ...@@ -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 ...@@ -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 @@ ...@@ -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; ...@@ -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) ...@@ -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; ...@@ -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;
......
/*
* 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());
}
/*
* 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 @@ ...@@ -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 ...@@ -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;
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(), uri_utf8); return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
} }
const char * const char *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment