From 38edb580540119b103e9c4ad9979e7b99e425dd2 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 17 Aug 2018 23:02:13 +0200 Subject: [PATCH 01/10] increment version number to 0.20.22 --- NEWS | 2 ++ android/AndroidManifest.xml | 4 ++-- configure.ac | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 21a2138d7..a1a7475d0 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,5 @@ +ver 0.20.22 (not yet released) + ver 0.20.21 (2018/08/17) * database - proxy: add "password" setting diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b6abd1124..7b6ed9afc 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="21" + android:versionName="0.20.22"> diff --git a/configure.ac b/configure.ac index 1d33ffb94..c14f1a7c0 100644 --- a/configure.ac +++ b/configure.ac @@ -1,10 +1,10 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.20.21, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.20.22, musicpd-dev-team@lists.sourceforge.net) VERSION_MAJOR=0 VERSION_MINOR=20 -VERSION_REVISION=21 +VERSION_REVISION=22 VERSION_EXTRA=0 AC_CONFIG_SRCDIR([src/Main.cxx]) From 147872fe9778b7019dcde72a14b1a667b950e422 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 11 Jan 2018 17:26:41 +0100 Subject: [PATCH 02/10] lib/curl/Easy: add curl_easy_escape() wrapper --- src/lib/curl/Easy.hxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/curl/Easy.hxx b/src/lib/curl/Easy.hxx index 0e51b159c..b6f0281e4 100644 --- a/src/lib/curl/Easy.hxx +++ b/src/lib/curl/Easy.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Max Kellermann + * Copyright (C) 2016-2018 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -86,6 +86,10 @@ public: if (code != CURLE_OK) throw std::runtime_error(curl_easy_strerror(code)); } + + char *Escape(const char *string, int length=0) const noexcept { + return curl_easy_escape(handle, string, length); + } }; #endif From 29f78b18b1dd77afd517a519afe4acd23dd76407 Mon Sep 17 00:00:00 2001 From: Joshua Wise Date: Fri, 17 Aug 2018 14:41:29 -0400 Subject: [PATCH 03/10] storage/plugins/CurlStorage: URL-encode paths in CurlStorage::MapUTF8 When using a database that was not created with a WebDAV music_directory (i.e., if using a remote database, on which updates happen locally) and using the Curl storage plugin, MPD would previously send GET requests that had unescaped spaces in them. This change uses Curl's URL-encode API to solve this. --- NEWS | 2 ++ src/storage/plugins/CurlStorage.cxx | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index a1a7475d0..972b7144a 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.20.22 (not yet released) +* storage + - curl: URL-encode paths ver 0.20.21 (2018/08/17) * database diff --git a/src/storage/plugins/CurlStorage.cxx b/src/storage/plugins/CurlStorage.cxx index 8e99f5c56..f2d61772e 100644 --- a/src/storage/plugins/CurlStorage.cxx +++ b/src/storage/plugins/CurlStorage.cxx @@ -34,6 +34,7 @@ #include "thread/Mutex.hxx" #include "thread/Cond.hxx" #include "util/ASCII.hxx" +#include "util/IterableSplitString.hxx" #include "util/RuntimeError.hxx" #include "util/StringCompare.hxx" #include "util/StringFormat.hxx" @@ -79,9 +80,18 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept if (StringIsEmpty(uri_utf8)) 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 * From 44a31357f41dcc066b6ffb8ad0866aab0e6f87db Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sat, 18 Aug 2018 20:44:18 +0200 Subject: [PATCH 04/10] android/AndroidManifest.xml: increase targetSdkVersion to 26 (required by Google Play) --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 7b6ed9afc..dba4f518f 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -5,7 +5,7 @@ android:versionCode="21" android:versionName="0.20.22"> - + Date: Sun, 19 Aug 2018 23:27:12 +0200 Subject: [PATCH 05/10] Makefile.am: use javac instead of javah to generate JNI header javah is deprecated. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index c1cda5605..de091bdd5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -333,11 +333,11 @@ android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R. @$(MKDIR_P) $(JAVA_CLASSFILES_DIR) $(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \ -cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \ + -h android/build/include \ -d $(JAVA_CLASSFILES_DIR) $^ $(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR) 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 From aff070bcbb771a5096f02b60c84aacfde601e043 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Sun, 2 Nov 2014 16:52:43 +0100 Subject: [PATCH 06/10] android: add LogListener A Java object to send logs on the android side. --- Makefile.am | 3 ++- android/src/Bridge.java | 8 ++++++- android/src/Main.java | 2 +- src/LogBackend.cxx | 5 ++++ src/Main.cxx | 7 +++++- src/Main.hxx | 3 +++ src/android/LogListener.cxx | 46 +++++++++++++++++++++++++++++++++++++ src/android/LogListener.hxx | 32 ++++++++++++++++++++++++++ 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/android/LogListener.cxx create mode 100644 src/android/LogListener.hxx diff --git a/Makefile.am b/Makefile.am index de091bdd5..495157e78 100644 --- a/Makefile.am +++ b/Makefile.am @@ -276,7 +276,8 @@ libjava_a_SOURCES = \ noinst_LIBRARIES += libandroid.a libandroid_a_SOURCES = \ 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 noinst_LIBRARIES += libmain.a diff --git a/android/src/Bridge.java b/android/src/Bridge.java index be8eabb6b..fad919204 100644 --- a/android/src/Bridge.java +++ b/android/src/Bridge.java @@ -25,6 +25,12 @@ import android.content.Context; * Bridge to native code. */ 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(); } diff --git a/android/src/Main.java b/android/src/Main.java index da64a1976..816b62cdb 100644 --- a/android/src/Main.java +++ b/android/src/Main.java @@ -69,7 +69,7 @@ public class Main extends Activity implements Runnable { } @Override public void run() { - Bridge.run(this); + Bridge.run(this, null); quitHandler.sendMessage(quitHandler.obtainMessage()); } } diff --git a/src/LogBackend.cxx b/src/LogBackend.cxx index ea3d2e2d6..323bcae91 100644 --- a/src/LogBackend.cxx +++ b/src/LogBackend.cxx @@ -34,6 +34,8 @@ #ifdef ANDROID #include +#include "android/LogListener.hxx" +#include "Main.hxx" static int ToAndroidLogLevel(LogLevel log_level) @@ -177,6 +179,9 @@ Log(const Domain &domain, LogLevel level, const char *msg) #ifdef ANDROID __android_log_print(ToAndroidLogLevel(level), "MPD", "%s: %s", domain.GetName(), msg); + if (logListener != nullptr) + logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level), + "%s: %s", domain.GetName(), msg); #else if (level < log_threshold) diff --git a/src/Main.cxx b/src/Main.cxx index 31c4d9fbd..f8fe3e93f 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -92,6 +92,7 @@ #include "java/File.hxx" #include "android/Environment.hxx" #include "android/Context.hxx" +#include "android/LogListener.hxx" #include "fs/StandardDirectory.hxx" #include "fs/FileSystem.hxx" #include "org_musicpd_Bridge.h" @@ -128,6 +129,7 @@ static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10; #ifdef ANDROID Context *context; +LogListener *logListener; #endif Instance *instance; @@ -676,16 +678,19 @@ try { gcc_visibility_default 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::File::Initialise(env); Environment::Initialise(env); context = new Context(env, _context); + if (_logListener != nullptr) + logListener = new LogListener(env, _logListener); mpd_main(0, nullptr); + delete logListener; delete context; Environment::Deinitialise(env); } diff --git a/src/Main.hxx b/src/Main.hxx index 9b41abf9f..f54b27767 100644 --- a/src/Main.hxx +++ b/src/Main.hxx @@ -25,7 +25,10 @@ class Context; struct Instance; #ifdef ANDROID +#include "android/LogListener.hxx" + extern Context *context; +extern LogListener *logListener; #endif extern Instance *instance; diff --git a/src/android/LogListener.cxx b/src/android/LogListener.cxx new file mode 100644 index 000000000..c7fed8811 --- /dev/null +++ b/src/android/LogListener.cxx @@ -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()); +} diff --git a/src/android/LogListener.hxx b/src/android/LogListener.hxx new file mode 100644 index 000000000..2c81078fe --- /dev/null +++ b/src/android/LogListener.hxx @@ -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 From 54a5491b86ce248fc56739aefe555d20e65d56a8 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Tue, 16 Sep 2014 12:00:59 +0200 Subject: [PATCH 07/10] android: Main is now a service - add Settings: Activity to start / stop MPD Service (Main). - Main is a service that run in foreground with a notification. See Service.startForeground documentation for more details. - Main.Client is used to control the service: start or stop it and also receive callbacks when service encounters an error, is killed, is started or is stopped. - Main.start to start the service without any fallback. --- Makefile.am | 15 +- NEWS | 3 + android/AndroidManifest.xml | 6 +- android/res/layout/custom_notification_gb.xml | 22 + android/res/values/strings.xml | 2 + android/src/IMain.aidl | 12 + android/src/IMainCallback.aidl | 9 + android/src/Main.java | 419 ++++++++++++++++-- android/src/Settings.java | 145 ++++++ 9 files changed, 593 insertions(+), 40 deletions(-) create mode 100644 android/res/layout/custom_notification_gb.xml create mode 100644 android/src/IMain.aidl create mode 100644 android/src/IMainCallback.aidl create mode 100644 android/src/Settings.java diff --git a/Makefile.am b/Makefile.am index 495157e78..fc0670220 100644 --- a/Makefile.am +++ b/Makefile.am @@ -301,6 +301,7 @@ ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_V ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM) JAVAC = javac +AIDL = $(ANDROID_BUILD_TOOLS_DIR)/aidl AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt DX = $(ANDROID_BUILD_TOOLS_DIR)/dx ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign @@ -308,11 +309,21 @@ ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml) 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_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) @$(MKDIR_P) $(dir $@) cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@ @@ -330,7 +341,7 @@ android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawabl # R.java is generated by aapt, when resources.apk is generated 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) $(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \ -cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \ diff --git a/NEWS b/NEWS index 972b7144a..5eb591817 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ ver 0.20.22 (not yet released) * storage - curl: URL-encode paths +* Android + - now runs as a service + - add button to start/stop MPD ver 0.20.21 (2018/08/17) * database diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index dba4f518f..6095c9460 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -8,14 +8,14 @@ - + + diff --git a/android/res/layout/custom_notification_gb.xml b/android/res/layout/custom_notification_gb.xml new file mode 100644 index 000000000..92a6036e2 --- /dev/null +++ b/android/res/layout/custom_notification_gb.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index 416c8de9f..bcc1ae0c5 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -2,4 +2,6 @@ MPD + Music Player Daemon is running + Touch for MPD options. diff --git a/android/src/IMain.aidl b/android/src/IMain.aidl new file mode 100644 index 000000000..ba7050d79 --- /dev/null +++ b/android/src/IMain.aidl @@ -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); +} diff --git a/android/src/IMainCallback.aidl b/android/src/IMainCallback.aidl new file mode 100644 index 000000000..c8cdaa4a0 --- /dev/null +++ b/android/src/IMainCallback.aidl @@ -0,0 +1,9 @@ +package org.musicpd; + +interface IMainCallback +{ + void onStarted(); + void onStopped(); + void onError(String error); + void onLog(int priority, String msg); +} diff --git a/android/src/Main.java b/android/src/Main.java index 816b62cdb..44e2e3d54 100644 --- a/android/src/Main.java +++ b/android/src/Main.java @@ -19,57 +19,406 @@ package org.musicpd; -import android.app.Activity; -import android.os.Bundle; +import android.annotation.TargetApi; +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.Handler; -import android.os.Message; -import android.widget.TextView; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.util.Log; +import android.widget.RemoteViews; -public class Main extends Activity implements Runnable { - private static final String TAG = "MPD"; +public class Main extends Service implements Runnable { + 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 mCallbacks = new RemoteCallbackList(); + private final IBinder mBinder = new MainStub(this); + private PowerManager.WakeLock mWakelock = null; - final Handler quitHandler = new Handler() { - public void handleMessage(Message msg) { - textView.setText("Music Player Daemon has quit"); + static class MainStub extends IMain.Stub { + private Main mService; + MainStub(Main service) { + mService = service; + } + public void start() { + mService.start(); + } + public void stop() { + mService.stop(); + } + public void 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.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.icon, + contentIntent); + else + notification = buildNotificationGB( + R.string.notification_title_mpd_running, + R.string.notification_text_mpd_running, + R.drawable.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) { - super.onCreate(savedInstanceState); + private final ServiceConnection mServiceConnection = new ServiceConnection() { - if (!Loader.loaded) { - TextView tv = new TextView(this); - tv.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=" + Loader.error); - setContentView(tv); - return; + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (this) { + mIMain = IMain.Stub.asInterface(service); + try { + if (mCallback != null) + mIMain.registerCallback(mICallback); + } catch (RemoteException e) { + if (mCallback != null) + mCallback.onError(REMOTE_ERROR); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (mCallback != null) + mCallback.onError(REMOTE_ERROR); + } + }; + + public Client(Context context, Callback cb) throws IllegalArgumentException { + if (context == null) + throw new IllegalArgumentException("Context can't be null"); + mContext = context; + mCallback = cb; + mBound = mContext.bindService(new Intent(mContext, Main.class), mServiceConnection, Context.BIND_AUTO_CREATE); } - if (thread == null || !thread.isAlive()) { - thread = new Thread(this, "NativeMain"); - thread.start(); + public boolean start() { + synchronized (this) { + if (mIMain != null) { + try { + mIMain.start(); + return true; + } catch (RemoteException e) { + } + } + return false; + } } - textView = new TextView(this); - textView.setText("Music Player Daemon is running" - + "\nCAUTION: this version is EXPERIMENTAL!"); - setContentView(textView); + public boolean stop() { + synchronized (this) { + if (mIMain != null) { + 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, null); - quitHandler.sendMessage(quitHandler.obtainMessage()); + /* + * start Main service without any callback + */ + public static void start(Context context, boolean wakelock) { + context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock)); } } diff --git a/android/src/Settings.java b/android/src/Settings.java new file mode 100644 index 000000000..89f96447d --- /dev/null +++ b/android/src/Settings.java @@ -0,0 +1,145 @@ +/* + * 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 android.app.Activity; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.LinearLayout; +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 static final int MSG_ERROR = 0; + private static final int MSG_STOPPED = 1; + private static final int MSG_STARTED = 2; + + 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); + 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); + break; + case MSG_STARTED: + Log.d(TAG, "onStarted"); + mTextView.setText("Music Player Daemon is running" + + "\nCAUTION: this version is EXPERIMENTAL!"); + mButton.setChecked(true); + break; + } + return true; + } + }); + + private OnCheckedChangeListener mOnCheckedChangeListener = new OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mClient != null) { + if (isChecked) + mClient.start(); + else + mClient.stop(); + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + mTextView = new TextView(this); + mTextView.setText(""); + + mButton = new ToggleButton(this); + mButton.setOnCheckedChangeListener(mOnCheckedChangeListener); + + mLayout = new LinearLayout(this); + mLayout.setOrientation(LinearLayout.VERTICAL); + mLayout.addView(mButton); + mLayout.addView(mTextView); + + setContentView(mLayout); + + super.onCreate(savedInstanceState); + } + + @Override + protected void onStart() { + mClient = new Main.Client(this, new Main.Client.Callback() { + @Override + public void onStopped() { + mHandler.removeCallbacksAndMessages(null); + mHandler.sendEmptyMessage(MSG_STOPPED); + } + + @Override + public void onStarted() { + mHandler.removeCallbacksAndMessages(null); + mHandler.sendEmptyMessage(MSG_STARTED); + } + + @Override + public void onError(String error) { + mHandler.removeCallbacksAndMessages(null); + mHandler.sendMessage(Message.obtain(mHandler, MSG_ERROR, error)); + } + + @Override + public void onLog(int priority, String msg) { + } + }); + super.onStart(); + } + + @Override + protected void onStop() { + mClient.release(); + mClient = null; + super.onStop(); + } +} From ef38dbe5bfe2dd4bca4a392dc4322a2d5855c2c6 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Sat, 18 Oct 2014 19:00:57 +0200 Subject: [PATCH 08/10] android: fix AndroidManifest.xml warnings - must be before - specify allowBackup (default) --- android/AndroidManifest.xml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 6095c9460..3a43a5e7d 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -7,7 +7,13 @@ - + + + + + @@ -18,7 +24,4 @@ - - - From f37ab5482b187d1f18427239ad055da4e751bcb4 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Sat, 18 Oct 2014 19:02:23 +0200 Subject: [PATCH 09/10] 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 --- NEWS | 1 + android/AndroidManifest.xml | 8 +- android/res/layout/log_item.xml | 5 + android/res/layout/settings.xml | 37 +++++++ android/res/values/strings.xml | 4 + android/src/Receiver.java | 41 +++++++ android/src/Settings.java | 189 +++++++++++++++++++++++++------- 7 files changed, 244 insertions(+), 41 deletions(-) create mode 100644 android/res/layout/log_item.xml create mode 100644 android/res/layout/settings.xml create mode 100644 android/src/Receiver.java diff --git a/NEWS b/NEWS index 5eb591817..69adaf45e 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3a43a5e7d..5dddf034e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -10,6 +10,7 @@ + - + + + + + + diff --git a/android/res/layout/log_item.xml b/android/res/layout/log_item.xml new file mode 100644 index 000000000..e6e74c913 --- /dev/null +++ b/android/res/layout/log_item.xml @@ -0,0 +1,5 @@ + + diff --git a/android/res/layout/settings.xml b/android/res/layout/settings.xml new file mode 100644 index 000000000..46e471b05 --- /dev/null +++ b/android/res/layout/settings.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index bcc1ae0c5..fc5a15bda 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -4,4 +4,8 @@ MPD Music Player Daemon is running Touch for MPD options. + MPD is running + MPD is not running + Run MPD automatically on boot + Prevent suspend when MPD is running (Wakelock) diff --git a/android/src/Receiver.java b/android/src/Receiver.java new file mode 100644 index 000000000..e24a29fbc --- /dev/null +++ b/android/src/Receiver.java @@ -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); + } + } + } +} diff --git a/android/src/Settings.java b/android/src/Settings.java index 89f96447d..69b5305e2 100644 --- a/android/src/Settings.java +++ b/android/src/Settings.java @@ -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 mLogListArray = new LinkedList(); + private ListView mLogListView; + private ArrayAdapter 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(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(); } From 14d3a7ae83cd8724308a82775ee8946befcfe435 Mon Sep 17 00:00:00 2001 From: Thomas Guillem Date: Sun, 19 Oct 2014 15:46:50 +0200 Subject: [PATCH 10/10] android: use a gray notification icon --- Makefile.am | 5 ++++- android/src/Main.java | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index fc0670220..6e2d96cae 100644 --- a/Makefile.am +++ b/Makefile.am @@ -328,7 +328,7 @@ $(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES) @$(MKDIR_P) $(dir $@) 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 $(AAPT) package -f -m --auto-add-overlay \ --custom-package org.musicpd \ @@ -362,6 +362,9 @@ android/build/res/drawable/icon.png: mpd.svg mkdir -p $(@D) 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 android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/$(ANDROID_ABI)/libmpd.so cp android/build/resources.apk $@ diff --git a/android/src/Main.java b/android/src/Main.java index 44e2e3d54..5a5d9d048 100644 --- a/android/src/Main.java +++ b/android/src/Main.java @@ -206,19 +206,19 @@ public class Main extends Service implements Runnable { notification = buildNotificationJB( R.string.notification_title_mpd_running, R.string.notification_text_mpd_running, - R.drawable.icon, + 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.icon, + R.drawable.notification_icon, contentIntent); else notification = buildNotificationGB( R.string.notification_title_mpd_running, R.string.notification_text_mpd_running, - R.drawable.icon, + R.drawable.notification_icon, contentIntent); startForeground(R.string.notification_title_mpd_running, notification);