package com.dataart.android.devicehive.network; import java.util.LinkedList; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import com.dataart.android.devicehive.DeviceHive; /** * Android {@link Service} subclass which is used to concurrently execute * commands sent via {@link Intent}. This service should be declared in * AndroidManifest.xml file. Can execute commands either concurrently or in FIFO * order. * * @see {@link NetworkCommand#isSerial()} */ public class DeviceHiveApiService extends Service { private final static String NAMESPACE = DeviceHiveApiService.class .getName(); /* package */final static String EXTRA_COMMAND = NAMESPACE .concat(".EXTRA_COMMAND"); /* package */final static String EXTRA_COMMAND_CONFIG = NAMESPACE .concat(".EXTRA_COMMAND_CONFIG"); /* package */final static String EXTRA_COMMAND_SERIAL = NAMESPACE .concat(".EXTRA_COMMAND_SERIAL"); private final static ThreadFactory threadFactory = new ThreadFactory() { private final AtomicInteger threadSerialNumber = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { final Thread thread = new Thread(r, "[DeviceHiveApiService #" + threadSerialNumber.getAndIncrement() + "]"); thread.setDaemon(true); return thread; } }; private final ConcurrentLinkedQueue<Integer> commandStartIdQueue = new ConcurrentLinkedQueue<Integer>(); private final static ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( 4, 6, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10), threadFactory); private final static SerialExecutor SERIAL_EXECUTOR = new SerialExecutor(); private static class SerialExecutor implements Executor { private final LinkedList<Runnable> tasks = new LinkedList<Runnable>(); private Runnable activeTask; public synchronized void execute(final Runnable r) { tasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (activeTask == null) { scheduleNext(); } } protected synchronized void scheduleNext() { activeTask = tasks.poll(); if (activeTask != null) { THREAD_POOL_EXECUTOR.execute(activeTask); } } } @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { commandStartIdQueue.add(startId); final Runnable runnable = new Runnable() { @Override public void run() { try { handleIntent(intent); } finally { stopSelf(commandStartIdQueue.remove()); } } }; if (intent.getBooleanExtra(EXTRA_COMMAND_SERIAL, false)) { SERIAL_EXECUTOR.execute(runnable); } else { THREAD_POOL_EXECUTOR.execute(runnable); } return START_NOT_STICKY; } protected void handleIntent(Intent intent) { NetworkCommand command = null; NetworkCommandConfig config = null; final long startTime = System.currentTimeMillis(); try { command = intent.getParcelableExtra(EXTRA_COMMAND); if (command != null) { config = intent.getParcelableExtra(EXTRA_COMMAND_CONFIG); if (config != null) { if (config.isDebugLoggingEnabled) { Log.d(DeviceHive.TAG, "Starting command " + command); } command.setConfig(config); command.execute(this); } else { Log.w(DeviceHive.TAG, "Missing command config in " + intent); } } else { Log.w(DeviceHive.TAG, "Missing command in " + intent); } } catch (Exception e) { Log.e(DeviceHive.TAG, "Cannot process command " + command, e); } finally { if (command != null && config != null) { if (config.isDebugLoggingEnabled) { Log.d(DeviceHive.TAG, "Completed command " + command + " in " + (System.currentTimeMillis() - startTime)); } } } } }