package org.cyanogenmod.tests.common; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.test.InstrumentationTestCase; import android.test.ServiceTestCase; /** * Tests a service in its own Thread. * * *

* The {@link ServiceTestCase} class creates the service in the same thread the * test is running. In consequence Handlers and other constructs that depend on * the fact that the service methods are always run on the main thread * won't work. *

* *

* To circumvent this, this test class creates a {@link HandlerThread} on setup * to simulate the main tread and provides helper constructs to ease the * communication between the Service and the test class : *

* * * * * @author Antoine Martin * */ public abstract class ThreadServiceTestCase extends ServiceTestCase { /** Typical maximum wait time for something to happen on the service */ public static final long WAIT_TIME = 5 * 1000; /* * This class provides final mutable values through indirection */ static class Holder { H value; } protected Handler serviceHandler; protected Looper serviceLooper; /* * Got to catch this again because of damn package visibility of * mServiceClass in base class. */ protected Class serviceClass; public ThreadServiceTestCase(Class serviceClass) { super(serviceClass); this.serviceClass = serviceClass; } @Override protected void setUp() throws Exception { super.setUp(); // Setup service thread HandlerThread serviceThread = new HandlerThread("[" + serviceClass.getSimpleName() + "Thread]"); serviceThread.start(); serviceLooper = serviceThread.getLooper(); serviceHandler = new Handler(serviceLooper); } @Override public void testServiceTestCaseSetUpProperly() throws Exception { } @Override protected void tearDown() throws Exception { super.tearDown(); // teardown service thread if (serviceLooper != null) serviceLooper.quit(); serviceHandler = null; } /** * Runs the specified runnable on the service tread and waits for its * completion. * * @see InstrumentationTestCase#runTestOnUiThread(Runnable) * @param r * The runnable to run on the pseudo-main thread. */ protected void runOnServiceThread(final Runnable r) { final CountDownLatch serviceSignal = new CountDownLatch(1); serviceHandler.post(new Runnable() { @Override public void run() { r.run(); serviceSignal.countDown(); } }); try { serviceSignal.await(); } catch (InterruptedException ie) { fail("The Service thread has been interrupted"); } } /** * Runnable interface allowing service initialization personalization. * * @author Antoine Martin * */ protected interface ServiceRunnable { public void run(Service service); } /** * Initialize the service in its own thread and returns it. * * @param bound * if {@code true}, the service will be created as if it was * bound by a client. if {@code false}, it will be created by a * {@code startService} call. * @param r * {@link ServiceRunnable} instance that will be called with the * created service. * @return The created service. */ protected T startService(final ServiceRunnable r) { final Holder serviceHolder = new Holder(); // I want to create my service in its own 'Main thread' // So it can use its handler runOnServiceThread(new Runnable() { @Override public void run() { T service = null; startService(new Intent(getContext(), serviceClass)); service = getService(); if (r != null) r.run(service); serviceHolder.value = service; } }); return serviceHolder.value; } protected IBinder bindService(final ServiceRunnable r) { final Holder serviceHolder = new Holder(); // I want to create my service in its own 'Main thread' // So it can use its handler runOnServiceThread(new Runnable() { @Override public void run() { T service = null; IBinder binder = bindService(new Intent(getContext(), serviceClass)); service = getService(); if (r != null) r.run(service); serviceHolder.value = binder; } }); return serviceHolder.value; } public static class ServiceSyncHelper { // The semaphore will wakeup clients protected final Semaphore semaphore = new Semaphore(0); /** * Waits for some response coming from the service. * * @param timeout * The maximum time to wait. * @throws InterruptedException * if the Thread is interrupted or reaches the timeout. */ public synchronized void waitListener(long timeout) throws InterruptedException { if (!semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS)) throw new InterruptedException(); } } }