/** * Creates a Goro implementation that binds to {@link com.stanfy.enroscar.goro.GoroService} * in order to run scheduled tasks in service context. * <p> * This method is functionally identical to * </p> * <pre> * BoundGoro goro = Goro.bindWith(context, new BoundGoro.OnUnexpectedDisconnection() { * public void onServiceDisconnected(BoundGoro goro) { * goro.bind(); * } * }); * </pre> * @param context context that will bind to the service * @return Goro implementation that binds to {@link GoroService}. * @see #bindWith(Context, BoundGoro.OnUnexpectedDisconnection) */ public static BoundGoro bindAndAutoReconnectWith(final Context context) { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } return new BoundGoroImpl(context, null); }
@Test public void getExecutorShouldReturnExecutorWrapper() { Executor executor = goro.getExecutor("q"); assertThat(executor).isNotNull(); Runnable task = mock(Runnable.class); executor.execute(task); verify(task, never()).run(); //noinspection NullableProblems Executor direct = new Executor() { @Override public void execute(Runnable command) { command.run(); } }; doReturn(direct).when(serviceInstance).getExecutor(anyString()); goro.bind(); verify(task).run(); }
@Test public void throwingTimeoutWithoutBinding() throws Exception { final Future<String> f = goro.schedule(okTask()); final String[] result = new String[1]; final Exception[] error = new Exception[1]; Thread t = new Thread() { @Override public void run() { try { result[0] = f.get(5, TimeUnit.MILLISECONDS); } catch (Exception e) { error[0] = e; } } }; t.start(); t.join(1000); assertThat(result).containsNull(); assertThat(error[0]).isInstanceOf(TimeoutException.class); }
@Override public void onServiceConnected(final ComponentName name, final IBinder binder) { synchronized (lock) { if (service != null) { throw new GoroException("Already bound and got onServiceConnected from " + name); } service = Goro.from(binder); // delegate listeners if (!scheduledListeners.taskListeners.isEmpty()) { for (GoroListener listener : scheduledListeners.taskListeners) { service.addTaskListener(listener); } scheduledListeners.taskListeners.clear(); } // delegate tasks if (!postponed.isEmpty()) { for (Postponed p : postponed) { p.act(service); } postponed.clear(); } if (oneshot) { doUnbindLocked(); } } }
/** * Creates a Goro implementation that binds to {@link GoroService} * in order to run scheduled tasks in service context. * {@code BoundGoro} exposes {@code bind()} and {@code unbind()} methods that you can use to connect to * and disconnect from the worker service in other component lifecycle callbacks * (like {@code onStart}/{@code onStop} in {@code Activity} or {@code onCreate}/{@code onDestory} in {@code Service}). * <p> * The worker service ({@code GoroService}) normally stops when all {@code BoundGoro} instances unbind * and all the pending tasks in {@code Goro} queues are handled. * But it can also be stopped by the system server (due to a user action in Settings app or application update). * In this case {@code BoundGoro} created with this method will notify the supplied handler about the event. * </p> * @param context context that will bind to the service * @param handler callback to invoke if worker service is unexpectedly stopped by the system server * @return Goro implementation that binds to {@link GoroService}. */ public static BoundGoro bindWith(final Context context, final BoundGoro.OnUnexpectedDisconnection handler) { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } if (handler == null) { throw new IllegalArgumentException("Disconnection handler cannot be null"); } return new BoundGoroImpl(context, handler); }
@Override public void onServiceDisconnected(final ComponentName name) { if (updateDelegate(null)) { /* It's the case when service was stopped by a system server. It can happen when user presses a stop button in application settings (in running apps section). Sometimes this happens on application update. */ if (disconnectionHandler == null) { bind(); } else { disconnectionHandler.onServiceDisconnected(this); } } }
@Override public synchronized boolean cancel(boolean mayInterruptIfRunning) { if (goroFuture != null) { return goroFuture.cancel(mayInterruptIfRunning); } if (!canceled) { cancelPostponed(this); pendingObservers = null; canceled = true; } notifyAll(); return true; }
@Override protected void doBinding() { goro.bind(); }
@Override public void onServiceConnected(final ComponentName name, final IBinder binder) { updateDelegate(Goro.from(binder)); if (unbindRequested) { // If unbind is requested before we get a real connection, unbind here, after delegating all buffered calls. unbind(); } }
@Override public void unbind() { synchronized (lock) { if (oneshot) { throw new IllegalStateException("bindOneshot() was already called. " + "You must call bind() to be able to call unbind()"); } doUnbindLocked(); } }
/** * Creates a Goro implementation that binds to {@link com.stanfy.enroscar.goro.GoroService} * in order to run scheduled tasks in service context. * @param context context that will bind to the service * @return Goro implementation that binds to {@link com.stanfy.enroscar.goro.GoroService}. */ public static BoundGoro bindWith(final Context context) { return new BoundGoroImpl(context); }
@Test public void unbindAfterConnectionIfRequested() { assertThat(Shadows.shadowOf(application).getUnboundServiceConnections()).isEmpty(); Robolectric.getForegroundThreadScheduler().pause(); goro.bind(); goro.unbind(); Robolectric.getForegroundThreadScheduler().unPause(); assertThat(Shadows.shadowOf(application).getUnboundServiceConnections()).hasSize(1); }
@Test public void clearShouldNotBePostponedAfterDelegation() { goro = (BoundGoro.BoundGoroImpl) Goro.bindAndAutoReconnectWith(context); goro.bind(); goro.clear("a"); goro.onServiceDisconnected(new ComponentName("any", "any")); verify(serviceInstance).clear("a"); }
@Test public void removeListenerShouldRemoveFromRecords() { GoroListener listener = mock(GoroListener.class); goro.addTaskListener(listener); goro.removeTaskListener(listener); goro.bind(); assertBinding(); verify(serviceInstance, never()).addTaskListener(listener); }
@Test public void autoReconnection() { goro = (BoundGoro.BoundGoroImpl) spy(Goro.bindAndAutoReconnectWith(context)); goro.bind(); assertBinding(); reset(goro); goro.onServiceDisconnected(new ComponentName("test", "test")); assertBinding(); }
@Test public void disconnectionHandlerIsInvoked() { goro.bind(); assertBinding(); goro.onServiceDisconnected(new ComponentName("test", "test")); verify(disconnection).onServiceDisconnected(goro); }
@Override public <T> ObservableFuture<T> schedule(final Callable<T> task) { return schedule(DEFAULT_QUEUE, task); }