/* 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.module; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.projectmaxs.shared.global.GlobalConstants; import org.projectmaxs.shared.global.jul.JULHandler; import org.projectmaxs.shared.global.messagecontent.Text; import org.projectmaxs.shared.global.util.Log; import org.projectmaxs.shared.mainmodule.Command; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; /** * MAXSModuleIntentService is meant for modules to handle their PERFORM_COMMAND * intents. * * @author Florian Schmaus flo@freakempire.de * */ public abstract class MAXSModuleIntentService extends Service { static { JULHandler.setAsDefaultUncaughtExceptionHandler(); } private static final int WHAT = 42; private final Log mLog; private final String mName; private final Map<String, SupraCommand> mCommands; private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private volatile Set<Object> mPendingActions = Collections .newSetFromMap(new ConcurrentHashMap<Object, Boolean>()); private String mVersion; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent) msg.obj); if (!hasMessages(WHAT) && mPendingActions.isEmpty()) { mLog.d("handleMessage: stopSelf hasMessasges=" + hasMessages(WHAT) + " actionsEmpty=" + mPendingActions.isEmpty() + " startId=" + msg.arg1); stopSelf(msg.arg1); } } } public MAXSModuleIntentService(Log log, String name, SupraCommand[] commands) { super(); mLog = log; mName = name; mCommands = new HashMap<String, SupraCommand>(commands.length); for (SupraCommand command : commands) { mCommands.put(command.getCommand(), command); // Note that we don't need to insert the short command here // If a command is given by the user in the short version, main will already substitute // the short for the long version for us } } @Override public void onCreate() { super.onCreate(); initLog(this); HandlerThread thread = new HandlerThread("MAXSModuleIntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); int versionResource = -1; try { Class<?> r = Class.forName(getPackageName() + ".R$string"); Field versionField = r.getField("version"); versionResource = (Integer) versionField.get(null); } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { mLog.e("Exception when retrieving version resource ID with reflection", e); } if (versionResource != -1) { mVersion = getString(versionResource); } else { mVersion = "Unknown"; } } @Override public void onDestroy() { mServiceLooper.quit(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; msg.what = 42; mServiceHandler.sendMessage(msg); return START_NOT_STICKY; } public final void addPendingAction(Object action) { mPendingActions.add(action); } public final void removePendingAction(Object action) { mPendingActions.remove(action); if (!mServiceHandler.hasMessages(WHAT) && mPendingActions.isEmpty()) { mLog.d("removePendingAction: stopSelf hasMessasges=" + mServiceHandler.hasMessages(WHAT) + " actionsEmpty=" + mPendingActions.isEmpty()); stopSelf(); } } protected final void onHandleIntent(Intent intent) { mLog.d("onHandleIntent: " + intent.getAction()); Command command = intent.getParcelableExtra(GlobalConstants.EXTRA_COMMAND); org.projectmaxs.shared.global.Message message = null; try { SupraCommand supraCommand = mCommands.get(command.getCommand()); if (supraCommand != null) { SubCommand subCommand = supraCommand.getSubCommand(command.getSubCommand()); if (subCommand != null) { if (subCommand.requiresArgument() && command.getArgs().isEmpty()) { throw new IllegalArgumentException( "This command requires an argument but none was given"); } else { message = subCommand.execute(command.getArgs(), command, this); } } else { throw new UnknownSubcommandException(command); } } else { throw new UnknownCommandException(command); } } catch (Throwable e) { mLog.e("onHandleIntent", e); Text text = new Text(); text.addBold("Exception").addNL(" handling command " + command + ": " + e.getMessage()); text.addItalic("Version: ").addNL(mVersion); // Let's also include the stacktrace as String text.addWithNewLines(android.util.Log.getStackTraceString(e)); text.addBoldNL("Further Info"); text.addItalic("OS Version: ").addNL( System.getProperty("os.version") + " (" + Build.VERSION.INCREMENTAL + ")"); text.addItalic("OS API Level: ").addNL(Integer.toString(Build.VERSION.SDK_INT)); text.addItalic("Device: ").addNL(Build.DEVICE); text.addItalic("Model (and Product): ").addNL(Build.MODEL + " (" + Build.PRODUCT + ")"); message = new org.projectmaxs.shared.global.Message(text); } if (message == null) return; // make sure the id is set send(message, command.getId()); } public abstract void initLog(Context context); public final void send(org.projectmaxs.shared.global.Message message, int cmdId) { message.setId(cmdId); send(message); } public final void send(org.projectmaxs.shared.global.Message message) { MainUtil.send(message, this); } }