diff --git a/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java b/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java
index 7d085918..50fc0ac7 100644
--- a/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java
+++ b/app/src/main/java/com/nutomic/syncthingandroid/activities/DeviceActivity.java
@@ -14,11 +14,15 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.EditText;
+import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -29,6 +33,7 @@ import com.google.zxing.integration.android.IntentResult;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.model.Connections;
import com.nutomic.syncthingandroid.model.Device;
+import com.nutomic.syncthingandroid.model.DiscoveredDevice;
import com.nutomic.syncthingandroid.service.Constants;
import com.nutomic.syncthingandroid.service.RestApi;
import com.nutomic.syncthingandroid.service.SyncthingService;
@@ -42,12 +47,19 @@ import com.nutomic.syncthingandroid.util.Util;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import javax.inject.Inject;
+import static android.support.v4.view.MarginLayoutParamsCompat.setMarginEnd;
+import static android.support.v4.view.MarginLayoutParamsCompat.setMarginStart;
import static android.text.TextUtils.isEmpty;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
import static com.nutomic.syncthingandroid.util.Compression.METADATA;
/**
@@ -75,6 +87,8 @@ public class DeviceActivity extends SyncthingActivity {
private Device mDevice;
private EditText mEditDeviceId;
+ private TextView mDiscoveredDevicesTitle;
+ private ViewGroup mDiscoveredDevicesContainer;
private View mShowDeviceIdContainer;
private EditText mShowDeviceId;
private View mQrButton;
@@ -182,6 +196,8 @@ public class DeviceActivity extends SyncthingActivity {
setTitle(mIsCreateMode ? R.string.add_device : R.string.edit_device);
mEditDeviceId = findViewById(R.id.editDeviceId);
+ mDiscoveredDevicesTitle = findViewById(R.id.discoveredDevicesTitle);
+ mDiscoveredDevicesContainer = findViewById(R.id.discoveredDevicesContainer);
mShowDeviceIdContainer = findViewById(R.id.showDeviceIdContainer);
mShowDeviceId = findViewById(R.id.showDeviceId);
mQrButton = findViewById(R.id.qrButton);
@@ -276,6 +292,9 @@ public class DeviceActivity extends SyncthingActivity {
RestApi restApi = syncthingService.getApi();
if (restApi != null) {
restApi.getConnections(this::onReceiveConnections);
+ if (mIsCreateMode) {
+ asyncQueryDiscoveredDevices(restApi);
+ }
}
}
@@ -615,4 +634,83 @@ public class DeviceActivity extends SyncthingActivity {
.create();
mDiscardDialog.show();
}
+
+ /**
+ * Perform asynchronous query via REST to retrieve locally discovered devices.
+ * Precondition:
+ * restApi != null
+ * mIsCreateMode == true
+ */
+ private void asyncQueryDiscoveredDevices(RestApi restApi) {
+ if (!restApi.isConfigLoaded()) {
+ return;
+ }
+ restApi.getDiscoveredDevices(this::onReceiveDiscoveredDevices);
+ }
+
+ /**
+ * Callback after {@link asyncQueryDiscoveredDevices}.
+ * Precondition:
+ * mIsCreateMode == true
+ */
+ private void onReceiveDiscoveredDevices(Map discoveredDevices) {
+ if (discoveredDevices == null) {
+ Log.e(TAG, "onReceiveDiscoveredDevices: discoveredDevices == null");
+ return;
+ }
+
+ mDiscoveredDevicesContainer.removeAllViews();
+ if (discoveredDevices.size() == 0) {
+ // No discovered devices.
+ int height = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics());
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(WRAP_CONTENT, height);
+ int dividerInset = getResources().getDimensionPixelOffset(R.dimen.material_divider_inset);
+ int contentInset = getResources().getDimensionPixelOffset(R.dimen.abc_action_bar_content_inset_material);
+ setMarginStart(params, dividerInset);
+ setMarginEnd(params, contentInset);
+ TextView emptyView = new TextView(mDiscoveredDevicesContainer.getContext());
+ emptyView.setGravity(CENTER_VERTICAL);
+ emptyView.setText(R.string.discovered_device_list_empty);
+ mDiscoveredDevicesContainer.addView(emptyView, params);
+ return;
+ }
+
+ for (String deviceId : discoveredDevices.keySet()) {
+ if (deviceId != null) {
+ // Get device address.
+ String readableAddresses = "";
+ DiscoveredDevice discoveredDevice = discoveredDevices.get(deviceId);
+ if (discoveredDevice != null && discoveredDevice.addresses != null) {
+ readableAddresses = TextUtils.join(", ", discoveredDevice.addresses);
+ // Log.v(TAG, "onReceiveDiscoveredDevices: deviceID = '" + deviceId + "' has addresses '" + readableAddresses + "'");
+ }
+ String caption = deviceId + (TextUtils.isEmpty(readableAddresses) ? "" : " (" + readableAddresses + ")");
+ LayoutInflater inflater = getLayoutInflater();
+ inflater.inflate(R.layout.item_discovered_device_form, mDiscoveredDevicesContainer);
+ TextView deviceIdView = (TextView) mDiscoveredDevicesContainer.getChildAt(mDiscoveredDevicesContainer.getChildCount()-1);
+ deviceIdView.setOnClickListener(null);
+ deviceIdView.setText(caption);
+ deviceIdView.setTag(deviceId);
+ deviceIdView.setOnClickListener(v -> onDeviceIdViewClick(v));
+ }
+ }
+
+ /**
+ * If "mEditDeviceId" already contains content, don't show local discovery results.
+ * This also suppresses the results being shown a second time after the user chose a
+ * deviceId from the list and rotated the screen.
+ */
+ mDiscoveredDevicesTitle.setVisibility(TextUtils.isEmpty(mEditDeviceId.getText()) ? View.VISIBLE : View.GONE);
+ mDiscoveredDevicesContainer.setVisibility(TextUtils.isEmpty(mEditDeviceId.getText()) ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Copies the deviceId from TextView to "device_id" EditText.
+ * Hides the "mDiscoveredDevicesContainer" view afterwards.
+ */
+ private void onDeviceIdViewClick(View view) {
+ mEditDeviceId.setText((String) view.getTag());
+ mDiscoveredDevicesTitle.setVisibility(View.GONE);
+ mDiscoveredDevicesContainer.setVisibility(View.GONE);
+ }
}
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java b/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java
index 64d7498d..242ac086 100644
--- a/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java
+++ b/app/src/main/java/com/nutomic/syncthingandroid/http/GetRequest.java
@@ -18,6 +18,7 @@ public class GetRequest extends ApiRequest {
public static final String URI_CONFIG = "/rest/system/config";
public static final String URI_DEBUG = "/rest/system/debug";
+ public static final String URI_SYSTEM_DISCOVERY = "/rest/system/discovery";
public static final String URI_VERSION = "/rest/system/version";
public static final String URI_SYSTEM_STATUS = "/rest/system/status";
public static final String URI_CONNECTIONS = "/rest/system/connections";
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/model/DiscoveredDevice.java b/app/src/main/java/com/nutomic/syncthingandroid/model/DiscoveredDevice.java
new file mode 100644
index 00000000..b373abb2
--- /dev/null
+++ b/app/src/main/java/com/nutomic/syncthingandroid/model/DiscoveredDevice.java
@@ -0,0 +1,22 @@
+package com.nutomic.syncthingandroid.model;
+
+import java.util.Map;
+
+/**
+ * This receives the deserialization result of the URI_SYSTEM_DISCOVERY query.
+ *
+ * JSON result example
+ * {
+ * "2MY7NNQ-IRBZIFP-B2V574Y-AX6FNIP-55VGH5H-GUD3RFV-K2RXX6P-XXXXXX":
+ * {
+ * "addresses":
+ * [
+ * "tcp4://192.168.178.10:40001"
+ * ]
+ * }
+ * }
+ *
+ */
+public class DiscoveredDevice {
+ public String[] addresses;
+}
diff --git a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java
index 4040a73f..23c5bb5e 100644
--- a/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java
+++ b/app/src/main/java/com/nutomic/syncthingandroid/service/RestApi.java
@@ -26,6 +26,7 @@ import com.nutomic.syncthingandroid.model.Completion;
import com.nutomic.syncthingandroid.model.CompletionInfo;
import com.nutomic.syncthingandroid.model.Connections;
import com.nutomic.syncthingandroid.model.Device;
+import com.nutomic.syncthingandroid.model.DiscoveredDevice;
import com.nutomic.syncthingandroid.model.DiskEvent;
import com.nutomic.syncthingandroid.model.Event;
import com.nutomic.syncthingandroid.model.Folder;
@@ -618,6 +619,23 @@ public class RestApi {
}
}
+ /**
+ * Requests locally discovered devices.
+ */
+ public void getDiscoveredDevices(OnResultListener1
Device ID
+
+ Discovered devices - Tap to select
+
+
+ Local discovery didn\'t find any devices on the local network.
+
Name