/*
This file is part of Project MAXS.
MAXS and its modules 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 3 of the License, or
(at your option) any later version.
MAXS 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 MAXS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.projectmaxs.shared.transport;
import java.lang.Thread.UncaughtExceptionHandler;
import org.projectmaxs.shared.global.jul.JULHandler;
import org.projectmaxs.shared.global.util.Log;
import org.projectmaxs.shared.maintransport.TransportConstants;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
public abstract class MAXSTransportService extends Service {
static {
JULHandler.setAsDefaultUncaughtExceptionHandler();
}
private static final Log LOG = Log.getLog();
private static final String IS_RUNNING_KEY = "isRunning";
/**
* Non persistent state boolean, used to track differences in the persistently saved state.
*/
private static volatile boolean sIsRunning = false;
/**
* SharedPreferences exclusively to persistently save the service state.
*/
private static SharedPreferences sSharedPreferences;
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private final String mName;
/**
* The actual class of this service. Required to issue START/STOP actions to it in case its
* state went out of sync.
*/
private final Class<?> mServiceClass;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent) msg.obj);
}
}
public MAXSTransportService(String name, Class<?> serviceClass) {
super();
mName = name;
mServiceClass = serviceClass;
}
@Override
public void onCreate() {
super.onCreate();
final String threadName = "MAXSTransportService[" + mName + "]";
HandlerThread thread = new HandlerThread(threadName);
thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
LOG.e(threadName + " thread: uncaught Exception!", ex);
}
});
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
sSharedPreferences = getSharedPreferences("MAXSTransportService", Context.MODE_PRIVATE);
}
@Override
public void onDestroy() {
super.onDestroy();
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void performInServiceHandler(Intent intent) {
Message msg = mServiceHandler.obtainMessage();
msg.obj = intent;
msg.what = intent.getAction().hashCode();
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
LOG.d("onStartCommand: null intent received, issuing START_SERVICE");
intent = new Intent(TransportConstants.ACTION_START_SERVICE);
} else if (isRunning() != sSharedPreferences.getBoolean(IS_RUNNING_KEY, false)) {
// The persistently saved state, returned by isRunning(), is different from the one the
// static boolean reports. Recover the persistent state.
String action;
if (!isRunning()) {
// Service is not running, but the persistent state says it was. Restore the running
// state.
action = TransportConstants.ACTION_START_SERVICE;
} else {
// This case should not really happen. Usually the situation is that a running
// service got killed by Android and is then restarted without a null intent
// (probably because there is an intent to be delivered at restart time). In which
// case isRunning() should return 'true', but sIsRunning is set to 'false'.
action = TransportConstants.ACTION_STOP_SERVICE;
}
LOG.i("onStartCommand: Service running state went out of sync: isRunning()="
+ isRunning() + ". Start service with action: " + action);
Intent startOrStopIntent = new Intent(action);
startOrStopIntent.setClass(this, mServiceClass);
startService(startOrStopIntent);
}
LOG.d("onStartCommand begin: intent=" + intent.getAction() + " flags=" + flags + " startId="
+ startId + " isRunning=" + isRunning());
boolean stickyStart = true;
final String action = intent.getAction();
if (TransportConstants.ACTION_STOP_SERVICE.equals(action)) {
setIsRunning(false);
stickyStart = false;
} else if (TransportConstants.ACTION_START_SERVICE.equals(action)) {
setIsRunning(true);
}
// If the service is not running, and we receive something else then
// START_SERVICE, then don't start sticky to prevent the service from
// running
else if (!isRunning() && !TransportConstants.ACTION_START_SERVICE.equals(action)) {
LOG.d("onStartCommand: service not running and action (" + action
+ ") not start. Don't start sticky");
stickyStart = false;
}
performInServiceHandler(intent);
LOG.d("onStartCommand result: stickyStart=" + stickyStart + " action=" + action);
return stickyStart ? START_STICKY : START_NOT_STICKY;
}
private static void setIsRunning(boolean isRunning) {
sSharedPreferences.edit().putBoolean(IS_RUNNING_KEY, isRunning).apply();
sIsRunning = isRunning;
}
public static boolean isRunning() {
return sIsRunning;
}
protected boolean hasMessage(int what) {
return mServiceHandler.hasMessages(what);
}
protected abstract void onHandleIntent(Intent intent);
}