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> listener) { + new GetRequest(mContext, mUrl, GetRequest.URI_SYSTEM_DISCOVERY, mApiKey, + null, result -> { + Map discoveredDevices = mGson.fromJson(result, new TypeToken>(){}.getType()); + if (ENABLE_TEST_DATA) { + DiscoveredDevice fakeDiscoveredDevice = new DiscoveredDevice(); + fakeDiscoveredDevice.addresses = new String[]{"tcp4://192.168.178.10:40004"}; + discoveredDevices.put("ZOK75WR-W3XWWUZ-NNLXV7V-DUYKVWA-SSPD7OH-3QYOZBY-SBH3N2Y-IAVJ4QH", fakeDiscoveredDevice); + discoveredDevices.put("ZPUZOWC-SUCJILE-ITNLBLL-MHBWJG5-46QM47Y-CDTQT3M-IA4RSJV-7BYA7QA", fakeDiscoveredDevice); + } + listener.onResult(discoveredDevices); + }); + } + /** * Requests ignore list for given folder. */ diff --git a/app/src/main/res/layout/fragment_device.xml b/app/src/main/res/layout/fragment_device.xml index 68a82e92..20dc5c00 100644 --- a/app/src/main/res/layout/fragment_device.xml +++ b/app/src/main/res/layout/fragment_device.xml @@ -56,6 +56,26 @@ + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 782d15bb..e98f8239 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -257,6 +257,12 @@ Bitte melden Sie auftretende Probleme via GitHub. Geräte-ID + + Gefundene Geräte - Tippe zur Auswahl + + + Die lokale Geräteerkennung hat im lokalen Netzwerk keine Geräte gefunden. + Name diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 47a27c4e..5e5779fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -260,6 +260,12 @@ Please report any problems you encounter via Github. Device ID + + Discovered devices - Tap to select + + + Local discovery didn\'t find any devices on the local network. + Name