diff --git a/res/layout/repodiscoveryitem.xml b/res/layout/repodiscoveryitem.xml
index c7fc54f88..b621839d7 100644
--- a/res/layout/repodiscoveryitem.xml
+++ b/res/layout/repodiscoveryitem.xml
@@ -10,7 +10,6 @@
android:maxLines="1"
android:paddingLeft="8sp"
android:paddingStart="8sp"
- android:text="@string/discovered_repo_name"
android:textSize="16sp"
android:textStyle="bold" />
@@ -23,7 +22,7 @@
android:paddingLeft="8sp"
android:paddingStart="8sp"
android:maxLines="1"
- android:text="@string/repo_address"
+ android:text="@string/waiting_for_ipaddress"
android:textSize="14sp" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5b21f4912..263bb5da5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -165,6 +165,7 @@
Local FDroid Repos
Discovering local FDroid repos…
Your local FDroid repo is accessible.
+ waiting for IP address…
Setup Local Repo
Touch to setup your local repo.
Touch to turn on your local repo.
@@ -251,9 +252,6 @@
Your device is not on the same WiFi as the local repo you just added! Try joining this network: %s
Requires: %1$s
- Discovered Repo Name
- Repo Address
-
App icon
Repo icon
diff --git a/src/org/fdroid/fdroid/net/MDnsHelper.java b/src/org/fdroid/fdroid/net/MDnsHelper.java
new file mode 100644
index 000000000..48e2bc853
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/MDnsHelper.java
@@ -0,0 +1,249 @@
+
+package org.fdroid.fdroid.net;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.fdroid.fdroid.R;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jmdns.*;
+
+public class MDnsHelper implements ServiceListener {
+
+ public static final String TAG = "MDnsHelper";
+ public static final String HTTP_SERVICE_TYPE = "_http._tcp.local.";
+ public static final String HTTPS_SERVICE_TYPE = "_https._tcp.local.";
+
+ final Activity mActivity;
+ final RepoScanListAdapter mAdapter;
+
+ private JmDNS mJmdns;
+
+ public MDnsHelper(Activity activity, final RepoScanListAdapter adapter) {
+ mActivity = activity;
+ mAdapter = adapter;
+ }
+
+ @Override
+ public void serviceAdded(ServiceEvent event) {
+ // a ListView Adapter can only be updated on the UI thread
+ final ServiceInfo serviceInfo = event.getInfo();
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.addItem(serviceInfo);
+ }
+ });
+ }
+
+ @Override
+ public void serviceRemoved(ServiceEvent event) {
+ // a ListView Adapter can only be updated on the UI thread
+ final ServiceInfo serviceInfo = event.getInfo();
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.removeItem(serviceInfo);
+ }
+ });
+ }
+
+ @Override
+ public void serviceResolved(ServiceEvent event) {
+ // a ListView Adapter can only be updated on the UI thread
+ final ServiceInfo serviceInfo = event.getInfo();
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.addItem(serviceInfo);
+ }
+ });
+ }
+
+ public void discoverServices() {
+ new AsyncTask() {
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ mJmdns = JmDNS.create();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (mJmdns != null) {
+ mJmdns.addServiceListener(HTTP_SERVICE_TYPE, MDnsHelper.this);
+ mJmdns.addServiceListener(HTTPS_SERVICE_TYPE, MDnsHelper.this);
+ }
+ }
+ }.execute();
+ }
+
+ public void stopDiscovery() {
+ if (mJmdns == null)
+ return;
+ mJmdns.removeServiceListener(HTTP_SERVICE_TYPE, MDnsHelper.this);
+ mJmdns.removeServiceListener(HTTPS_SERVICE_TYPE, MDnsHelper.this);
+ mJmdns = null;
+ }
+
+ public static class RepoScanListAdapter extends BaseAdapter {
+ private Context mContext;
+ private LayoutInflater mLayoutInflater;
+ private List mEntries = new ArrayList();
+
+ public RepoScanListAdapter(Context context) {
+ mContext = context;
+ mLayoutInflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ @Override
+ public int getCount() {
+ return mEntries.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mEntries.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ DiscoveredRepo service = mEntries.get(position);
+ ServiceInfo serviceInfo = service.getServiceInfo();
+ InetAddress[] addresses = serviceInfo.getInetAddresses();
+ if (addresses != null && addresses.length > 0)
+ return true;
+ else
+ return false;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ RelativeLayout itemView;
+ if (convertView == null) {
+ itemView = (RelativeLayout) mLayoutInflater.inflate(
+ R.layout.repodiscoveryitem, parent, false);
+ } else {
+ itemView = (RelativeLayout) convertView;
+ }
+
+ TextView nameLabel = (TextView) itemView.findViewById(R.id.reposcanitemname);
+ TextView addressLabel = (TextView) itemView.findViewById(R.id.reposcanitemaddress);
+
+ final DiscoveredRepo service = mEntries.get(position);
+ final ServiceInfo serviceInfo = service.getServiceInfo();
+
+ nameLabel.setText(serviceInfo.getName());
+
+ InetAddress[] addresses = serviceInfo.getInetAddresses();
+ if (addresses != null && addresses.length > 0) {
+ String addressTxt = "Hosted @ " + addresses[0] + ":" + serviceInfo.getPort();
+ addressLabel.setText(addressTxt);
+ }
+
+ return itemView;
+ }
+
+ public void addItem(ServiceInfo item) {
+ if (item == null || item.getName() == null)
+ return;
+
+ // Construct a DiscoveredRepo wrapper for the service being
+ // added in order to use a name based equals().
+ DiscoveredRepo newDRepo = new DiscoveredRepo(item);
+ // if an unresolved entry with the same name exists, remove it
+ for (DiscoveredRepo dr : mEntries)
+ if (dr.equals(newDRepo)) {
+ InetAddress[] addresses = dr.mServiceInfo.getInetAddresses();
+ if (addresses == null || addresses.length == 0)
+ mEntries.remove(dr);
+ }
+ mEntries.add(newDRepo);
+
+ notifyUpdate();
+ }
+
+ public void removeItem(ServiceInfo item) {
+ if (item == null || item.getName() == null)
+ return;
+
+ // Construct a DiscoveredRepo wrapper for the service being
+ // removed in order to use a name based equals().
+ DiscoveredRepo lostServiceBean = new DiscoveredRepo(item);
+
+ if (mEntries.contains(lostServiceBean)) {
+ mEntries.remove(lostServiceBean);
+ notifyUpdate();
+ }
+ }
+
+ private void notifyUpdate() {
+ // Need to call notifyDataSetChanged from the UI thread
+ // in order for it to update the ListView without error
+ Handler refresh = new Handler(Looper.getMainLooper());
+ refresh.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyDataSetChanged();
+ }
+ });
+ }
+ }
+
+ public static class DiscoveredRepo {
+ private final ServiceInfo mServiceInfo;
+
+ public DiscoveredRepo(ServiceInfo serviceInfo) {
+ if (serviceInfo == null || serviceInfo.getName() == null)
+ throw new IllegalArgumentException(
+ "Parameters \"serviceInfo\" and \"name\" must not be null.");
+ mServiceInfo = serviceInfo;
+ }
+
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getName() {
+ return mServiceInfo.getName();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof DiscoveredRepo))
+ return false;
+
+ // Treat two services the same based on name. Eventually
+ // there should be a persistent mapping between fingerprint
+ // of the repo key and the discovered service such that we
+ // could maintain trust across hostnames/ips/networks
+ DiscoveredRepo otherRepo = (DiscoveredRepo) other;
+ return getName().equals(otherRepo.getName());
+ }
+ }
+}
diff --git a/src/org/fdroid/fdroid/net/NsdHelper.java b/src/org/fdroid/fdroid/net/NsdHelper.java
deleted file mode 100644
index b56dbe37c..000000000
--- a/src/org/fdroid/fdroid/net/NsdHelper.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package org.fdroid.fdroid.net;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.net.nsd.NsdServiceInfo;
-import android.net.nsd.NsdManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import org.fdroid.fdroid.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@TargetApi(16) // AKA Android 4.1 AKA Jelly Bean
-public class NsdHelper {
-
- public static final String TAG = "NsdHelper";
- public static final String HTTP_SERVICE_TYPE = "_fdroidrepo._tcp.";
- public static final String HTTPS_SERVICE_TYPE = "_fdroidrepos._tcp.";
-
- final Context mContext;
- final NsdManager mNsdManager;
- final RepoScanListAdapter mAdapter;
-
- NsdManager.ResolveListener mResolveListener;
- NsdManager.DiscoveryListener mDiscoveryListener;
-
- public NsdHelper(Context context, final RepoScanListAdapter adapter) {
- mContext = context;
- mAdapter = adapter;
- mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
-
- initializeResolveListener();
- initializeDiscoveryListener();
- }
-
- public void initializeDiscoveryListener() {
- mDiscoveryListener = new NsdManager.DiscoveryListener() {
-
- @Override
- public void onDiscoveryStarted(String regType) {
- Log.i(TAG, "Service discovery started");
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo service)
- {
- Log.d(TAG, "Discovered service: "+ service.getServiceName() +
- " Type: "+ service.getServiceType());
-
- if (service.getServiceType().equals(HTTP_SERVICE_TYPE) ||
- service.getServiceType().equals(HTTPS_SERVICE_TYPE))
- {
- Log.d(TAG, "Resolving FDroid service");
- mNsdManager.resolveService(service, mResolveListener);
- }
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo service) {
- Log.e(TAG, "service lost" + service);
- mAdapter.removeItem(service);
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- Log.i(TAG, "Discovery stopped: " + serviceType);
- }
-
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code:" + errorCode);
- mNsdManager.stopServiceDiscovery(this);
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- Log.e(TAG, "Discovery failed: Error code:" + errorCode);
- mNsdManager.stopServiceDiscovery(this);
- }
- };
- }
-
- public void initializeResolveListener() {
- mResolveListener = new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- Log.e(TAG, "Resolve failed: Error code: " + errorCode);
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- Log.d(TAG, "Resolve Succeeded. " + serviceInfo);
- mAdapter.addItem(serviceInfo);
- }
- };
- }
-
- public void discoverServices() {
- mNsdManager.discoverServices(
- HTTP_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
- mNsdManager.discoverServices(
- HTTPS_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
- }
-
- public void stopDiscovery() {
- mNsdManager.stopServiceDiscovery(mDiscoveryListener);
- }
-
- public static class RepoScanListAdapter extends BaseAdapter {
- private Context mContext;
- private LayoutInflater mLayoutInflater;
- private List mEntries = new ArrayList();
-
- public RepoScanListAdapter(Context context) {
- mContext = context;
- mLayoutInflater = (LayoutInflater) mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- @Override
- public int getCount() {
- return mEntries.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mEntries.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- RelativeLayout itemView;
- if (convertView == null)
- {
- itemView = (RelativeLayout) mLayoutInflater.inflate(
- R.layout.repodiscoveryitem, parent, false);
- } else {
- itemView = (RelativeLayout) convertView;
- }
-
- TextView nameLabel = (TextView) itemView.findViewById(R.id.reposcanitemname);
- TextView addressLabel = (TextView) itemView.findViewById(R.id.reposcanitemaddress);
-
- final DiscoveredRepo service = mEntries.get(position);
- final NsdServiceInfo serviceInfo = service.getServiceInfo();
-
- String addressTxt = "Hosted @ "+
- serviceInfo.getHost().getHostAddress() + ":"+ serviceInfo.getPort();
-
- nameLabel.setText(serviceInfo.getServiceName());
- addressLabel.setText(addressTxt);
-
- return itemView;
- }
-
- public void addItem(NsdServiceInfo item)
- {
- if(item == null || item.getServiceName() == null)
- return;
-
- //Construct a DiscoveredRepo wrapper for the service being
- //added in order to use a name based equals().
- DiscoveredRepo repoBean = new DiscoveredRepo(item);
- mEntries.add(repoBean);
-
- notifyUpdate();
- }
-
- public void removeItem(NsdServiceInfo item)
- {
- if(item == null || item.getServiceName() == null)
- return;
-
- //Construct a DiscoveredRepo wrapper for the service being
- //removed in order to use a name based equals().
- DiscoveredRepo lostServiceBean = new DiscoveredRepo(item);
-
- if(mEntries.contains(lostServiceBean))
- {
- mEntries.remove(lostServiceBean);
- notifyUpdate();
- }
- }
-
- private void notifyUpdate()
- {
- //Need to call notifyDataSetChanged from the UI thread
- //in order for it to update the ListView without error
- Handler refresh = new Handler(Looper.getMainLooper());
- refresh.post(new Runnable() {
- @Override
- public void run()
- {
- notifyDataSetChanged();
- }
- });
- }
- }
-
- public static class DiscoveredRepo {
- private final NsdServiceInfo mServiceInfo;
-
- public DiscoveredRepo(NsdServiceInfo serviceInfo)
- {
- if(serviceInfo == null || serviceInfo.getServiceName() == null)
- throw new IllegalArgumentException(
- "Parameters \"serviceInfo\" and \"name\" must not be null.");
- mServiceInfo = serviceInfo;
- }
-
- public NsdServiceInfo getServiceInfo()
- {
- return mServiceInfo;
- }
-
- public String getName()
- {
- return mServiceInfo.getServiceName();
- }
-
- @Override
- public boolean equals(Object other)
- {
- if(!(other instanceof DiscoveredRepo))
- return false;
-
- //Treat two services the same based on name. Eventually
- //there should be a persistent mapping between fingerprint
- //of the repo key and the discovered service such that we
- //could maintain trust across hostnames/ips/networks
- DiscoveredRepo otherRepo = (DiscoveredRepo) other;
- return getName().equals(otherRepo.getName());
- }
- }
-}
-
-
-
diff --git a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java
index 0fc2b8fa1..7423d7bc2 100644
--- a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java
+++ b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java
@@ -1,19 +1,11 @@
package org.fdroid.fdroid.views.fragments;
-import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
+import android.content.*;
import android.database.Cursor;
import android.net.Uri;
-import android.net.nsd.NsdServiceInfo;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
@@ -22,31 +14,18 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
-import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
+import android.view.*;
+import android.widget.*;
-import org.fdroid.fdroid.FDroidApp;
-import org.fdroid.fdroid.net.NsdHelper;
-import org.fdroid.fdroid.net.NsdHelper.DiscoveredRepo;
-import org.fdroid.fdroid.Preferences;
-import org.fdroid.fdroid.ProgressListener;
-import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.net.NsdHelper.RepoScanListAdapter;
-import org.fdroid.fdroid.UpdateService;
+import org.fdroid.fdroid.*;
import org.fdroid.fdroid.compat.ClipboardCompat;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
+import org.fdroid.fdroid.net.MDnsHelper;
+import org.fdroid.fdroid.net.MDnsHelper.DiscoveredRepo;
+import org.fdroid.fdroid.net.MDnsHelper.RepoScanListAdapter;
import org.fdroid.fdroid.views.RepoAdapter;
import org.fdroid.fdroid.views.RepoDetailsActivity;
@@ -55,6 +34,8 @@ import java.net.URL;
import java.util.Date;
import java.util.Locale;
+import javax.jmdns.ServiceInfo;
+
public class RepoListFragment extends ListFragment
implements LoaderManager.LoaderCallbacks, RepoAdapter.EnabledListener {
@@ -246,57 +227,56 @@ public class RepoListFragment extends ListFragment
});
}
- @TargetApi(16) // AKA Android 4.1 AKA Jelly Bean
private void scanForRepos() {
- final Activity a = getActivity();
+ final Activity activity = getActivity();
- final RepoScanListAdapter adapter = new RepoScanListAdapter(a);
- final NsdHelper nsdHelper = new NsdHelper(a.getApplicationContext(), adapter);
+ final RepoScanListAdapter adapter = new RepoScanListAdapter(activity);
+ final MDnsHelper mDnsHelper = new MDnsHelper(activity, adapter);
final View view = getLayoutInflater(null).inflate(R.layout.repodiscoverylist, null);
final ListView repoScanList = (ListView) view.findViewById(R.id.reposcanlist);
final AlertDialog alrt = new AlertDialog.Builder(getActivity()).setView(view)
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- nsdHelper.stopDiscovery();
- dialog.dismiss();
- }
- }).create();
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mDnsHelper.stopDiscovery();
+ dialog.dismiss();
+ }
+ }).create();
alrt.setTitle(R.string.local_repos_title);
alrt.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
- nsdHelper.stopDiscovery();
+ mDnsHelper.stopDiscovery();
}
});
repoScanList.setAdapter(adapter);
repoScanList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, final View view,
- int position, long id) {
+ @Override
+ public void onItemClick(AdapterView> parent, final View view,
+ int position, long id) {
- final DiscoveredRepo discoveredService =
- (DiscoveredRepo) parent.getItemAtPosition(position);
+ final DiscoveredRepo discoveredService =
+ (DiscoveredRepo) parent.getItemAtPosition(position);
- final NsdServiceInfo serviceInfo = discoveredService.getServiceInfo();
+ final ServiceInfo serviceInfo = discoveredService.getServiceInfo();
- String serviceType = serviceInfo.getServiceType();
- String protocol = serviceType.contains("fdroidrepos") ? "https://" : "http://";
+ String serviceType = serviceInfo.getType();
+ // TODO get repo type from TXT record
+ String protocol = serviceType.contains("fdroidrepos") ? "https:/" : "http:/";
- String serviceAddress = protocol + serviceInfo.getHost().getHostAddress()
- + ":" + serviceInfo.getPort() + "/fdroid/repo";
- showAddRepo(serviceAddress, "");
- }
+ String serviceAddress = protocol + serviceInfo.getInetAddresses()[0]
+ + ":" + serviceInfo.getPort() + "/fdroid/repo";
+ // TODO get path from TXT record
+ showAddRepo(serviceAddress, "");
+ }
});
alrt.show();
-
- Log.d("FDroid", "Starting network service discovery");
- nsdHelper.discoverServices();
+ mDnsHelper.discoverServices();
}
public void importRepo(String uri, String fingerprint) {
@@ -416,7 +396,8 @@ public class RepoListFragment extends ListFragment
ContentValues values = new ContentValues(2);
values.put(RepoProvider.DataColumns.ADDRESS, address);
if (fingerprint != null && fingerprint.length() > 0) {
- values.put(RepoProvider.DataColumns.FINGERPRINT, fingerprint.toUpperCase(Locale.ENGLISH));
+ values.put(RepoProvider.DataColumns.FINGERPRINT,
+ fingerprint.toUpperCase(Locale.ENGLISH));
}
RepoProvider.Helper.insert(getActivity(), values);
finishedAddingRepo();