Main.java 9.65 KB
Newer Older
Max Kellermann's avatar
Max Kellermann committed
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
Max Kellermann's avatar
Max Kellermann committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 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;

22 23 24 25 26 27 28 29
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;
Max Kellermann's avatar
Max Kellermann committed
30
import android.os.Build;
31 32 33 34
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
Max Kellermann's avatar
Max Kellermann committed
35
import android.util.Log;
36
import android.widget.RemoteViews;
Max Kellermann's avatar
Max Kellermann committed
37

38 39 40 41 42 43
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;
Max Kellermann's avatar
Max Kellermann committed
44

45 46
	private static final int MSG_SEND_STATUS = 0;
	private static final int MSG_SEND_LOG = 1;
Max Kellermann's avatar
Max Kellermann committed
47

48 49 50 51 52 53 54
	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;
55

56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
	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);
		}
	}
80

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
	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) {
106
			}
107 108 109
		}
		mCallbacks.finishBroadcast();
	}
110

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
	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;
	}
Max Kellermann's avatar
Max Kellermann committed
130

131 132
	@Override
	public void run() {
Max Kellermann's avatar
Max Kellermann committed
133
		if (!Loader.loaded) {
134 135 136 137 138 139 140 141
			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();
Max Kellermann's avatar
Max Kellermann committed
142 143
			return;
		}
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
		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);

171 172 173 174 175 176
		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();
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

		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);
		}
	}
Max Kellermann's avatar
Max Kellermann committed
226

227 228 229
	private void unregisterCallback(IMainCallback cb) {
		if (cb != null) {
			mCallbacks.unregister(cb);
Max Kellermann's avatar
Max Kellermann committed
230
		}
231 232 233 234 235 236
	}

	/*
	 * Client that bind the Main Service in order to send commands and receive callback
	 */
	public static class Client {
Max Kellermann's avatar
Max Kellermann committed
237

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
		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);
			}
		}
Max Kellermann's avatar
Max Kellermann committed
370 371
	}

372 373 374 375 376
	/*
	 * start Main service without any callback
	 */
	public static void start(Context context, boolean wakelock) {
		context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
Max Kellermann's avatar
Max Kellermann committed
377 378
	}
}