2014-02-18 08:33:06 +01:00
|
|
|
/*
|
2018-10-31 17:54:59 +01:00
|
|
|
* Copyright 2003-2018 The Music Player Daemon Project
|
2014-02-18 08:33:06 +01:00
|
|
|
* 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;
|
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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;
|
2014-02-18 08:33:06 +01:00
|
|
|
import android.os.Build;
|
2014-09-16 12:00:59 +02:00
|
|
|
import android.os.IBinder;
|
|
|
|
import android.os.PowerManager;
|
|
|
|
import android.os.RemoteCallbackList;
|
|
|
|
import android.os.RemoteException;
|
2014-02-18 08:33:06 +01:00
|
|
|
import android.util.Log;
|
2014-09-16 12:00:59 +02:00
|
|
|
import android.widget.RemoteViews;
|
2014-02-18 08:33:06 +01:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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;
|
2014-02-18 08:33:06 +01:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
private static final int MSG_SEND_STATUS = 0;
|
|
|
|
private static final int MSG_SEND_LOG = 1;
|
2014-02-18 08:33:06 +01:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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;
|
2014-07-30 10:12:18 +02:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2014-07-30 10:12:18 +02:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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) {
|
2014-07-30 10:12:18 +02:00
|
|
|
}
|
2014-09-16 12:00:59 +02:00
|
|
|
}
|
|
|
|
mCallbacks.finishBroadcast();
|
|
|
|
}
|
2014-07-30 10:12:18 +02:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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;
|
|
|
|
}
|
2014-02-18 08:33:06 +01:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
@Override
|
|
|
|
public void run() {
|
2014-02-18 08:33:06 +01:00
|
|
|
if (!Loader.loaded) {
|
2014-09-16 12:00:59 +02:00
|
|
|
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();
|
2014-02-18 08:33:06 +01:00
|
|
|
return;
|
|
|
|
}
|
2014-09-16 12:00:59 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-08-20 11:25:47 +02:00
|
|
|
Notification notification = new Notification.Builder(this)
|
|
|
|
.setContentTitle(getText(R.string.notification_title_mpd_running))
|
|
|
|
.setContentText(getText(R.string.notification_text_mpd_running))
|
|
|
|
.setSmallIcon(R.drawable.notification_icon)
|
|
|
|
.setContentIntent(contentIntent)
|
|
|
|
.build();
|
2014-09-16 12:00:59 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2014-02-18 08:33:06 +01:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
private void unregisterCallback(IMainCallback cb) {
|
|
|
|
if (cb != null) {
|
|
|
|
mCallbacks.unregister(cb);
|
2014-02-18 08:33:06 +01:00
|
|
|
}
|
2014-09-16 12:00:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Client that bind the Main Service in order to send commands and receive callback
|
|
|
|
*/
|
|
|
|
public static class Client {
|
2014-02-18 08:33:06 +01:00
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
|
|
|
|
|
|
|
@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);
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean start() {
|
|
|
|
synchronized (this) {
|
|
|
|
if (mIMain != null) {
|
|
|
|
try {
|
|
|
|
mIMain.start();
|
|
|
|
return true;
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2014-02-18 08:33:06 +01:00
|
|
|
}
|
|
|
|
|
2014-09-16 12:00:59 +02:00
|
|
|
/*
|
|
|
|
* start Main service without any callback
|
|
|
|
*/
|
|
|
|
public static void start(Context context, boolean wakelock) {
|
|
|
|
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
|
2014-02-18 08:33:06 +01:00
|
|
|
}
|
|
|
|
}
|