Skip to content

Commit

Permalink
Use AdbDataSource notifications to show "disconnected" status
Browse files Browse the repository at this point in the history
This is better than polling.

Issue: #330
  • Loading branch information
mlopatkin committed Dec 25, 2023
1 parent 27fb624 commit 6364780
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import java.util.Set;
import java.util.concurrent.Executor;

public final class AdbDataSource implements DataSource, BufferReceiver {
public class AdbDataSource implements DataSource, BufferReceiver {
/**
* The reason for the data source to become invalid.
*/
Expand All @@ -57,13 +57,12 @@ public interface StateObserver {
*
* @param reason the reason for invalidation
*/
void onDataSourceInvalidated(InvalidationReason reason);
default void onDataSourceInvalidated(InvalidationReason reason) {}

/**
* Called when the data source is closed whether normally or because of invalidation.
*/
default void onDataSourceClosed() {
}
default void onDataSourceClosed() {}
}

private static final Logger logger = Logger.getLogger(AdbDataSource.class);
Expand Down
31 changes: 16 additions & 15 deletions src/name/mlopatkin/andlogview/ui/status/SourceStatusPresenter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@
package name.mlopatkin.andlogview.ui.status;

import name.mlopatkin.andlogview.DataSourceHolder;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbDataSource;
import name.mlopatkin.andlogview.logmodel.DataSource;
import name.mlopatkin.andlogview.ui.device.AdbServicesStatus;
import name.mlopatkin.andlogview.ui.mainframe.MainFrameScoped;
import name.mlopatkin.andlogview.utils.CommonChars;
import name.mlopatkin.andlogview.utils.UiThreadScheduler;
import name.mlopatkin.andlogview.utils.events.Observable;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.time.Duration;
import java.util.function.Consumer;

import javax.inject.Inject;
Expand All @@ -39,7 +38,12 @@ public class SourceStatusPresenter {
private final DataSourceHolder dataSourceHolder;
private final AdbServicesStatus adbServicesStatus;
private final View view;
private final UiThreadScheduler updateScheduler;
private final AdbDataSource.StateObserver adbDataSourceObserver = new AdbDataSource.StateObserver() {
@Override
public void onDataSourceClosed() {
updateSourceStatus();
}
};

interface View {
void showWaitingStatus(String statusText);
Expand All @@ -54,12 +58,10 @@ interface View {
DataSourceHolder dataSourceHolder,
AdbServicesStatus adbServicesStatus,
View view,
SourcePopupMenuPresenter popupMenuPresenter,
UiThreadScheduler updateScheduler) {
SourcePopupMenuPresenter popupMenuPresenter) {
this.dataSourceHolder = dataSourceHolder;
this.adbServicesStatus = adbServicesStatus;
this.view = view;
this.updateScheduler = updateScheduler;

view.popupMenuAction().addObserver(popupMenuPresenter::showPopupMenuIfNeeded);
}
Expand All @@ -68,8 +70,8 @@ interface View {
void init() {
adbServicesStatus.asObservable().addObserver(this::onAdbServicesStatusChanged);
dataSourceHolder.asObservable().addObserver(this::onDataSourceChanged);
if (dataSourceHolder.getDataSource() != null) {
scheduleUpdates();
if (dataSourceHolder.getDataSource() instanceof AdbDataSource adbDataSource) {
adbDataSource.asStateObservable().addObserver(adbDataSourceObserver);
}
updateSourceStatus();
}
Expand All @@ -79,7 +81,7 @@ private void onAdbServicesStatusChanged(AdbServicesStatus.StatusValue statusValu
}

private void updateSourceStatus() {
DataSource dataSource = dataSourceHolder.getDataSource();
var dataSource = dataSourceHolder.getDataSource();
if (dataSource != null) {
view.showSourceStatus(dataSource.toString());
} else {
Expand All @@ -102,13 +104,12 @@ private String evaluateSourceStatus() {
}

private void onDataSourceChanged(@Nullable DataSource oldSource, DataSource newSource) {
if (oldSource == null) {
scheduleUpdates();
if (oldSource instanceof AdbDataSource adbDataSource) {
adbDataSource.asStateObservable().removeObserver(adbDataSourceObserver);
}
if (newSource instanceof AdbDataSource adbDataSource) {
adbDataSource.asStateObservable().addObserver(adbDataSourceObserver);
}
updateSourceStatus();
}

private void scheduleUpdates() {
updateScheduler.postRepeatableTask(this::updateSourceStatus, Duration.ofSeconds(2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package name.mlopatkin.andlogview.ui.status;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
Expand All @@ -24,9 +25,9 @@
import static org.mockito.Mockito.when;

import name.mlopatkin.andlogview.DataSourceHolder;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbDataSource;
import name.mlopatkin.andlogview.logmodel.DataSource;
import name.mlopatkin.andlogview.ui.device.AdbServicesStatus;
import name.mlopatkin.andlogview.utils.MockUiThreadScheduler;
import name.mlopatkin.andlogview.utils.events.Subject;

import org.junit.jupiter.api.BeforeEach;
Expand All @@ -39,9 +40,7 @@

@ExtendWith(MockitoExtension.class)
class SourceStatusPresenterTest {

@Mock
DataSourceHolder dataSourceHolder;
DataSourceHolder dataSourceHolder = new DataSourceHolder();

@Mock
AdbServicesStatus adbServicesStatus;
Expand All @@ -52,16 +51,12 @@ class SourceStatusPresenterTest {
@Mock
SourcePopupMenuPresenter popupMenuPresenter;

final MockUiThreadScheduler uiScheduler = new MockUiThreadScheduler();

final Subject<AdbServicesStatus.Observer> adbServicesStatusObservers = new Subject<>();
final Subject<DataSourceHolder.Observer> dataSourceHolderObservers = new Subject<>();
final Subject<Consumer<SourceStatusPopupMenuView>> viewObservers = new Subject<>();

@BeforeEach
void setUp() {
lenient().when(adbServicesStatus.asObservable()).thenReturn(adbServicesStatusObservers.asObservable());
lenient().when(dataSourceHolder.asObservable()).thenReturn(dataSourceHolderObservers.asObservable());
lenient().when(view.popupMenuAction()).thenReturn(viewObservers.asObservable());

lenient().when(adbServicesStatus.getStatus()).thenReturn(AdbServicesStatus.StatusValue.notInitialized());
Expand Down Expand Up @@ -172,20 +167,65 @@ void openedDataSourceReplacesPrevious() {
verify(view).showSourceStatus("new DS");
}

@Test
void closedAdbDataSourceIsNotified() {
var observers = withAdbDataSource("adb", "disconnected");

createPresenter();

verify(view).showSourceStatus("adb");

observers.forEach(AdbDataSource.StateObserver::onDataSourceClosed);

verify(view).showSourceStatus("disconnected");
}

@Test
void unsubscribesFromAdbDataSource() {
var observers = withAdbDataSource("1", "1");

createPresenter();
onDataSourceOpened("other");

assertThat(observers).isEmpty();
}

@Test
void subscribesToOpenedAdbDataSource() {
withDataSource("initial");

createPresenterNotTrackingView();
var observers = onAdbDataSourceOpened("adb", "disconnected");

verify(view).showSourceStatus("adb");

observers.forEach(AdbDataSource.StateObserver::onDataSourceClosed);

verify(view).showSourceStatus("disconnected");
}

private void createPresenter() {
var presenter =
new SourceStatusPresenter(dataSourceHolder, adbServicesStatus, view, popupMenuPresenter, uiScheduler);
new SourceStatusPresenter(dataSourceHolder, adbServicesStatus, view, popupMenuPresenter);
presenter.init();
}


private DataSource withDataSource(String name) {
var dataSource = mock(DataSource.class);
when(dataSource.toString()).thenReturn(name);
when(dataSourceHolder.getDataSource()).thenReturn(dataSource);
dataSourceHolder.setDataSource(dataSource);
return dataSource;
}

private Subject<AdbDataSource.StateObserver> withAdbDataSource(String name, String disconnectedName) {
var stateObservers = new Subject<AdbDataSource.StateObserver>();
var dataSource = mock(AdbDataSource.class);
when(dataSource.toString()).thenReturn(name, disconnectedName);
when(dataSource.asStateObservable()).thenReturn(stateObservers.asObservable());
dataSourceHolder.setDataSource(dataSource);
return stateObservers;
}

private void withAdbInitializing() {
when(adbServicesStatus.getStatus()).thenReturn(AdbServicesStatus.StatusValue.initializing());
}
Expand Down Expand Up @@ -213,10 +253,10 @@ private void onAdbFailure(String failure) {
}

private void onDataSourceOpened(String name) {
var newDataSource = withDataSource(name);
withDataSource(name);
}

for (DataSourceHolder.Observer obs : dataSourceHolderObservers) {
obs.onDataSourceChanged(null, newDataSource);
}
private Subject<AdbDataSource.StateObserver> onAdbDataSourceOpened(String name, String disconnectedName) {
return withAdbDataSource(name, disconnectedName);
}
}

0 comments on commit 6364780

Please sign in to comment.