Skip to content

Commit

Permalink
Rewrite disconnect notification handling a bit
Browse files Browse the repository at this point in the history
Instead of tracking a device, track the data source instead. It fixes a
discrepancy when the data source is already gone but the disconnect
handler still tracks the device.

Fixes #96
  • Loading branch information
mlopatkin committed Jan 6, 2022
1 parent 9b256a9 commit 2120d80
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 57 deletions.
67 changes: 19 additions & 48 deletions src/name/mlopatkin/andlogview/DeviceDisconnectedHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,83 +15,54 @@
*/
package name.mlopatkin.andlogview;

import name.mlopatkin.andlogview.device.AdbDevice;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbDeviceManager;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbDataSource;
import name.mlopatkin.andlogview.preferences.AdbConfigurationPref;

import com.android.ddmlib.IDevice;

import org.apache.log4j.Logger;

import java.awt.EventQueue;
import java.util.Objects;

import javax.swing.JOptionPane;

/**
* This class is responsible for showing notification dialog when device is
* disconnected.
* This class is responsible for showing notification dialog when device is disconnected.
*/
public class DeviceDisconnectedHandler extends AdbDeviceManager.AbstractDeviceListener {
private static final Logger logger = Logger.getLogger(DeviceDisconnectedHandler.class);
public class DeviceDisconnectedHandler implements AdbDataSource.StateObserver {
private final MainFrame mainFrame;
private final AdbConfigurationPref adbConfigurationPref;
private final AdbDeviceManager deviceManager;
private final IDevice device;

private DeviceDisconnectedHandler(MainFrame mainFrame, AdbConfigurationPref adbConfigurationPref,
AdbDeviceManager deviceManager, IDevice device) {
private DeviceDisconnectedHandler(MainFrame mainFrame, AdbConfigurationPref adbConfigurationPref) {
this.mainFrame = mainFrame;
this.adbConfigurationPref = adbConfigurationPref;
this.deviceManager = deviceManager;
this.device = device;
}

private boolean isTrackedDevice(IDevice device) {
return Objects.equals(device.getSerialNumber(), this.device.getSerialNumber());
}
@Override
public void deviceDisconnected(IDevice device) {
if (isTrackedDevice(device)) {
onDeviceDisconnected(disconnectedInvoker);
// one-shot
deviceManager.removeDeviceChangeListener(this);
public void onDataSourceInvalidated(AdbDataSource.InvalidationReason reason) {
switch (reason) {
case DISCONNECT:
onDeviceDisconnected("Device is disconnected");
break;
case OFFLINE:
onDeviceDisconnected("Device goes offline");
break;
default:
throw new AssertionError("Unexpected reason " + reason);
}
}

@Override
public void deviceChanged(IDevice device, int changeMask) {
if (isTrackedDevice(device) && (changeMask & IDevice.CHANGE_STATE) != 0) {
if (!device.isOnline()) {
onDeviceDisconnected(offlineInvoker);
deviceManager.removeDeviceChangeListener(this);
}
}
}

private void onDeviceDisconnected(Runnable notificationInvoker) {
logger.debug("showNotification");
private void onDeviceDisconnected(String message) {
if (adbConfigurationPref.isAutoReconnectEnabled()) {
mainFrame.waitForDevice();
} else {
EventQueue.invokeLater(notificationInvoker);
EventQueue.invokeLater(() -> showNotificationDialog(message));
}
}

private void showNotificationDialog(String message) {
assert EventQueue.isDispatchThread();
logger.debug("show notification dialog");
JOptionPane.showMessageDialog(mainFrame, message, "Warning", JOptionPane.WARNING_MESSAGE);
logger.debug("close notification dialog");
}

private Runnable disconnectedInvoker = () -> showNotificationDialog("Device is disconnected");

private Runnable offlineInvoker = () -> showNotificationDialog("Device goes offline");

public static void startWatching(MainFrame mainFrame, AdbConfigurationPref adbConfigurationPref,
AdbDeviceManager deviceManager, AdbDevice device) {
deviceManager.addDeviceChangeListener(
new DeviceDisconnectedHandler(mainFrame, adbConfigurationPref, deviceManager, device.getIDevice()));
public static void startWatching(AdbDataSource dataSource, MainFrame mainFrame,
AdbConfigurationPref adbConfigurationPref) {
dataSource.asStateObservable().addObserver(new DeviceDisconnectedHandler(mainFrame, adbConfigurationPref));
}
}
48 changes: 46 additions & 2 deletions src/name/mlopatkin/andlogview/liblogcat/ddmlib/AdbDataSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import name.mlopatkin.andlogview.liblogcat.RecordListener;
import name.mlopatkin.andlogview.liblogcat.SourceMetadata;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbBuffer.BufferReceiver;
import name.mlopatkin.andlogview.utils.events.Observable;
import name.mlopatkin.andlogview.utils.events.ScopedObserver;
import name.mlopatkin.andlogview.utils.events.Subject;

import org.apache.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -37,6 +39,33 @@
import java.util.Set;

public final class AdbDataSource implements DataSource, BufferReceiver {
/**
* The reason for the data source to become invalid.
*/
public enum InvalidationReason {
OFFLINE, DISCONNECT
}

/**
* The interface receives updates about the state of this data source.
*/
public interface StateObserver {
/**
* Called when the data source is no longer valid and will not produce any useful data anymore. The invalidation
* doesn't include calling {@link AdbDataSource#close()} as this is a part of the normal lifecycle. There is no
* need to close the data source after invalidation, it closes itself automatically.
*
* @param reason the reason for invalidation
*/
void onDataSourceInvalidated(InvalidationReason reason);

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

private static final Logger logger = Logger.getLogger(AdbDataSource.class);

private final AdbDevice device;
Expand All @@ -45,6 +74,7 @@ public final class AdbDataSource implements DataSource, BufferReceiver {
private final EnumSet<Buffer> availableBuffers = EnumSet.noneOf(Buffer.class);
private final SourceMetadata sourceMetadata;
private final ScopedObserver deviceChangeObserver;
private final Subject<StateObserver> stateObservers = new Subject<>();

private @Nullable RecordListener<LogRecord> listener;
private boolean closed = false;
Expand All @@ -62,14 +92,14 @@ public AdbDataSource(AdbDevice device, AdbDeviceList deviceList) {
@Override
public void onDeviceDisconnected(AdbDevice device) {
logger.debug("Device " + device.getSerialNumber() + " was disconnected, closing the source");
close();
invalidateAndClose(InvalidationReason.DISCONNECT);
}

@Override
public void onDeviceChanged(AdbDevice device) {
if (!device.isOnline()) {
logger.debug("Device " + device.getSerialNumber() + " is offline, closing the source");
close();
invalidateAndClose(InvalidationReason.OFFLINE);
}
}
}.scopeToSingleDevice(device));
Expand All @@ -83,6 +113,16 @@ public void close() {
converter.close();
deviceChangeObserver.close();
closed = true;
for (StateObserver stateObserver : stateObservers) {
stateObserver.onDataSourceClosed();
}
}

private void invalidateAndClose(InvalidationReason reason) {
for (StateObserver stateObserver : stateObservers) {
stateObserver.onDataSourceInvalidated(reason);
}
close();
}

@Override
Expand Down Expand Up @@ -174,4 +214,8 @@ public String toString() {
public SourceMetadata getMetadata() {
return sourceMetadata;
}

public Observable<StateObserver> asStateObservable() {
return stateObservers.asObservable();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import name.mlopatkin.andlogview.device.AdbDevice;
import name.mlopatkin.andlogview.device.AdbDeviceList;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbDataSource;
import name.mlopatkin.andlogview.liblogcat.ddmlib.AdbDeviceManager;
import name.mlopatkin.andlogview.preferences.AdbConfigurationPref;

import java.util.function.Consumer;
Expand All @@ -33,17 +32,14 @@ public class AdbDataSourceFactory {
private final MainFrame mainFrame;
private final SelectDeviceDialog.Factory selectDeviceDialogFactory;
private final AdbDeviceList adbDeviceList;
// TODO(mlopatkin) get rid of AdbDeviceManager injection here.
private final AdbDeviceManager adbDeviceManager;
private final AdbConfigurationPref adbConfigurationPref;

@Inject
AdbDataSourceFactory(MainFrame mainFrame, SelectDeviceDialog.Factory selectDeviceDialogFactory,
AdbDeviceList adbDeviceList, AdbDeviceManager adbDeviceManager, AdbConfigurationPref adbConfigurationPref) {
AdbDeviceList adbDeviceList, AdbConfigurationPref adbConfigurationPref) {
this.mainFrame = mainFrame;
this.selectDeviceDialogFactory = selectDeviceDialogFactory;
this.adbDeviceList = adbDeviceList;
this.adbDeviceManager = adbDeviceManager;
this.adbConfigurationPref = adbConfigurationPref;
}

Expand All @@ -56,7 +52,8 @@ public void selectDeviceAndOpenAsDataSource(Consumer<AdbDataSource> callback) {
}

public void openDeviceAsDataSource(AdbDevice device, Consumer<AdbDataSource> callback) {
DeviceDisconnectedHandler.startWatching(mainFrame, adbConfigurationPref, adbDeviceManager, device);
callback.accept(new AdbDataSource(device, adbDeviceList));
AdbDataSource dataSource = new AdbDataSource(device, adbDeviceList);
DeviceDisconnectedHandler.startWatching(dataSource, mainFrame, adbConfigurationPref);
callback.accept(dataSource);
}
}

0 comments on commit 2120d80

Please sign in to comment.