diff --git a/.classpath b/.classpath
index fd733198e..82469dc51 100644
--- a/.classpath
+++ b/.classpath
@@ -5,6 +5,7 @@
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 13d8b590d..2f02f3dbe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
+
@@ -53,6 +54,7 @@
android:name="FDroidApp"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
+ android:description="@string/app_description"
android:allowBackup="true"
android:theme="@style/AppThemeDark"
android:supportsRtl="true" >
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 de6073c47..179619796 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -34,7 +34,12 @@
Install using system-permissions
Use system permissions to install, update, and remove packages
Do not use system permissions to install, update, and remove packages
-
+ Broadcast Local Repo
+ Advertise your local repo using Bonjour (mDNS)
+ Do not advertise your local repo.
+ Name of your Local Repo
+ The advertised title of your local repo: %s
+
Search Results
App Details
No such app found
@@ -160,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.
@@ -246,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
@@ -277,5 +280,6 @@
The (de-)installation failed. If you are using root access, try disabling this setting!
System permissions denied
This option is only available when F-Droid is installed as a system-app.
-
+
+ F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device.
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index fa534900f..fce6f7cb3 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -43,6 +43,15 @@
android:defaultValue="false"
android:key="ignoreTouchscreen" />
+
+
+
+
compactLayoutListeners = new ArrayList();
private List filterAppsRequiringRootListeners = new ArrayList();
private List updateHistoryListeners = new ArrayList();
+ private List localRepoBonjourListeners = new ArrayList();
+ private List localRepoNameListeners = new ArrayList();
private boolean isInitialized(String key) {
return initialized.containsKey(key) && initialized.get(key);
@@ -74,6 +85,19 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
return preferences.getBoolean(PREF_SYSTEM_INSTALLER, DEFAULT_SYSTEM_INSTALLER);
}
+ public boolean isLocalRepoBonjourEnabled() {
+ return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR);
+ }
+
+ private String getDefaultLocalRepoName() {
+ return (Build.BRAND + " " + Build.MODEL + String.valueOf(new Random().nextInt(9999)))
+ .replaceAll(" ", "-");
+ }
+
+ public String getLocalRepoName() {
+ return preferences.getString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName());
+ }
+
public boolean hasCompactLayout() {
if (!isInitialized(PREF_COMPACT_LAYOUT)) {
initialize(PREF_COMPACT_LAYOUT);
@@ -146,6 +170,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
for ( ChangeListener listener : updateHistoryListeners ) {
listener.onPreferenceChange();
}
+ } else if (key.equals(PREF_LOCAL_REPO_BONJOUR)) {
+ for ( ChangeListener listener : localRepoBonjourListeners ) {
+ listener.onPreferenceChange();
+ }
+ } else if (key.equals(PREF_LOCAL_REPO_NAME)) {
+ for ( ChangeListener listener : localRepoNameListeners ) {
+ listener.onPreferenceChange();
+ }
}
}
@@ -157,6 +189,22 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
updateHistoryListeners.remove(listener);
}
+ public void registerLocalRepoBonjourListeners(ChangeListener listener) {
+ localRepoBonjourListeners.add(listener);
+ }
+
+ public void unregisterLocalRepoBonjourListeners(ChangeListener listener) {
+ localRepoBonjourListeners.remove(listener);
+ }
+
+ public void registerLocalRepoNameListeners(ChangeListener listener) {
+ localRepoNameListeners.add(listener);
+ }
+
+ public void unregisterLocalRepoNameListeners(ChangeListener listener) {
+ localRepoNameListeners.remove(listener);
+ }
+
public static interface ChangeListener {
public void onPreferenceChange();
}
diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java
index 55c36ec3e..15e532e4a 100644
--- a/src/org/fdroid/fdroid/PreferencesActivity.java
+++ b/src/org/fdroid/fdroid/PreferencesActivity.java
@@ -54,6 +54,8 @@ public class PreferencesActivity extends PreferenceActivity implements
Preferences.PREF_PERMISSIONS,
Preferences.PREF_COMPACT_LAYOUT,
Preferences.PREF_IGN_TOUCH,
+ Preferences.PREF_LOCAL_REPO_BONJOUR,
+ Preferences.PREF_LOCAL_REPO_NAME,
Preferences.PREF_CACHE_APK,
Preferences.PREF_EXPERT,
Preferences.PREF_ROOT_INSTALLER,
@@ -87,10 +89,9 @@ public class PreferencesActivity extends PreferenceActivity implements
pref.setSummary(pref.getEntry());
}
- protected void textSummary(String key) {
+ protected void textSummary(String key, int resId) {
EditTextPreference pref = (EditTextPreference)findPreference(key);
- pref.setSummary(getString(R.string.update_history_summ,
- pref.getText()));
+ pref.setSummary(getString(resId, pref.getText()));
}
protected void updateSummary(String key, boolean changing) {
@@ -117,7 +118,7 @@ public class PreferencesActivity extends PreferenceActivity implements
R.string.notify_off);
} else if (key.equals(Preferences.PREF_UPD_HISTORY)) {
- textSummary(key);
+ textSummary(key, R.string.update_history_summ);
} else if (key.equals(Preferences.PREF_PERMISSIONS)) {
onoffSummary(key, R.string.showPermissions_on,
@@ -146,6 +147,13 @@ public class PreferencesActivity extends PreferenceActivity implements
onoffSummary(key, R.string.ignoreTouch_on,
R.string.ignoreTouch_off);
+ } else if (key.equals(Preferences.PREF_LOCAL_REPO_BONJOUR)) {
+ onoffSummary(key, R.string.local_repo_bonjour_on,
+ R.string.local_repo_bonjour_off);
+
+ } else if (key.equals(Preferences.PREF_LOCAL_REPO_NAME)) {
+ textSummary(key, R.string.local_repo_name_summary);
+
} else if (key.equals(Preferences.PREF_CACHE_APK)) {
onoffSummary(key, R.string.cache_downloaded_on,
R.string.cache_downloaded_off);
diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java
index f071579a5..cd1322f10 100644
--- a/src/org/fdroid/fdroid/Utils.java
+++ b/src/org/fdroid/fdroid/Utils.java
@@ -23,7 +23,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.XmlResourceParser;
import android.net.Uri;
-import android.os.Build;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -118,7 +117,7 @@ public final class Utils {
try {
Process sh = Runtime.getRuntime().exec("sh");
OutputStream out = sh.getOutputStream();
- String command = "/system/bin/ln -s " + inFile.getAbsolutePath() + " " + outFile
+ String command = "/system/bin/ln -s " + inFile + " " + outFile
+ "\nexit\n";
out.write(command.getBytes("ASCII"));
@@ -453,8 +452,4 @@ public final class Utils {
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
- public static String getDefaultRepoName() {
- return (Build.BRAND + " " + Build.MODEL).replaceAll(" ", "-");
- }
-
}
diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java
index 5b21235a2..21108edd1 100644
--- a/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java
+++ b/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java
@@ -16,6 +16,7 @@ import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
+import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
@@ -125,10 +126,10 @@ public class LocalRepoManager {
// the user will always find the bootstrap page.
File fdroidDirIndex = new File(fdroidDir, "index.html");
fdroidDirIndex.delete();
- Utils.symlinkOrCopyFile(indexHtml, fdroidDirIndex);
+ Utils.symlinkOrCopyFile(new File("../index.html"), fdroidDirIndex);
File repoDirIndex = new File(repoDir, "index.html");
repoDirIndex.delete();
- Utils.symlinkOrCopyFile(indexHtml, repoDirIndex);
+ Utils.symlinkOrCopyFile(new File("../../index.html"), repoDirIndex);
// add in /FDROID/REPO to support bad QR Scanner apps
File fdroidCAPS = new File(fdroidDir.getParentFile(), "FDROID");
fdroidCAPS.mkdir();
@@ -136,10 +137,10 @@ public class LocalRepoManager {
repoCAPS.mkdir();
File fdroidCAPSIndex = new File(fdroidCAPS, "index.html");
fdroidCAPSIndex.delete();
- Utils.symlinkOrCopyFile(indexHtml, fdroidCAPSIndex);
+ Utils.symlinkOrCopyFile(new File("../index.html"), fdroidCAPSIndex);
File repoCAPSIndex = new File(repoCAPS, "index.html");
repoCAPSIndex.delete();
- Utils.symlinkOrCopyFile(indexHtml, repoCAPSIndex);
+ Utils.symlinkOrCopyFile(new File("../../index.html"), repoCAPSIndex);
} catch (IOException e) {
e.printStackTrace();
}
@@ -271,7 +272,7 @@ public class LocalRepoManager {
int repoMaxAge = Float.valueOf(prefs.getString("max_repo_age_days",
DEFAULT_REPO_MAX_AGE_DAYS)).intValue();
- String repoName = prefs.getString("repo_name", Utils.getDefaultRepoName());
+ String repoName = Preferences.get().getLocalRepoName();
Element repo = doc.createElement("repo");
repo.setAttribute("icon", "blah.png");
diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java
index c9d6511c6..690105fdc 100644
--- a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java
+++ b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java
@@ -2,26 +2,17 @@
package org.fdroid.fdroid.localrepo;
import android.annotation.SuppressLint;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
+import android.app.*;
+import android.content.*;
+import android.os.*;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.Preferences.ChangeListener;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.net.LocalHTTPD;
import org.fdroid.fdroid.net.WifiStateChangeService;
@@ -29,8 +20,12 @@ import org.fdroid.fdroid.views.LocalRepoActivity;
import java.io.IOException;
import java.net.BindException;
+import java.util.HashMap;
import java.util.Random;
+import javax.jmdns.JmDNS;
+import javax.jmdns.ServiceInfo;
+
public class LocalRepoService extends Service {
private static final String TAG = "LocalRepoService";
@@ -39,11 +34,15 @@ public class LocalRepoService extends Service {
public static final String STOPPED = "org.fdroid.fdroid.category.LOCAL_REPO_STOPPED";
private NotificationManager notificationManager;
+ private Notification notification;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.local_repo_running;
private Handler webServerThreadHandler = null;
+ private LocalHTTPD localHttpd;
+ private JmDNS jmdns;
+ private ServiceInfo pairService;
public static int START = 1111111;
public static int STOP = 12345678;
@@ -61,12 +60,12 @@ public class LocalRepoService extends Service {
@Override
public void handleMessage(Message msg) {
if (msg.arg1 == START) {
- service.startWebServer();
+ service.startNetworkServices();
} else if (msg.arg1 == STOP) {
- service.stopWebServer();
+ service.stopNetworkServices();
} else if (msg.arg1 == RESTART) {
- service.stopWebServer();
- service.startWebServer();
+ service.stopNetworkServices();
+ service.startNetworkServices();
} else {
Log.e(TAG, "unsupported msg.arg1, ignored");
}
@@ -76,8 +75,19 @@ public class LocalRepoService extends Service {
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent i) {
- stopWebServer();
- startWebServer();
+ stopNetworkServices();
+ startNetworkServices();
+ }
+ };
+
+ private ChangeListener localRepoBonjourChangeListener = new ChangeListener() {
+ @Override
+ public void onPreferenceChange() {
+ if (localHttpd.isAlive())
+ if (Preferences.get().isLocalRepoBonjourEnabled())
+ registerMDNSService();
+ else
+ unregisterMDNSService();
}
};
@@ -87,15 +97,18 @@ public class LocalRepoService extends Service {
// launch LocalRepoActivity if the user selects this notification
Intent intent = new Intent(this, LocalRepoActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
- Notification notification = new NotificationCompat.Builder(this)
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ notification = new NotificationCompat.Builder(this)
.setContentTitle(getText(R.string.local_repo_running))
.setContentText(getText(R.string.touch_to_configure_local_repo))
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentIntent(contentIntent)
.build();
startForeground(NOTIFICATION, notification);
- startWebServer();
+ startNetworkServices();
+ Preferences.get().registerLocalRepoBonjourListeners(localRepoBonjourChangeListener);
+
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
new IntentFilter(WifiStateChangeService.BROADCAST));
}
@@ -109,9 +122,10 @@ public class LocalRepoService extends Service {
@Override
public void onDestroy() {
- stopWebServer();
+ stopNetworkServices();
notificationManager.cancel(NOTIFICATION);
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
+ Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
}
@Override
@@ -119,6 +133,17 @@ public class LocalRepoService extends Service {
return messenger.getBinder();
}
+ private void startNetworkServices() {
+ startWebServer();
+ if (Preferences.get().isLocalRepoBonjourEnabled())
+ registerMDNSService();
+ }
+
+ private void stopNetworkServices() {
+ unregisterMDNSService();
+ stopWebServer();
+ }
+
private void startWebServer() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -127,7 +152,7 @@ public class LocalRepoService extends Service {
@SuppressLint("HandlerLeak")
@Override
public void run() {
- final LocalHTTPD localHttpd = new LocalHTTPD(getFilesDir(),
+ localHttpd = new LocalHTTPD(getFilesDir(),
prefs.getBoolean("use_https", false));
Looper.prepare(); // must be run before creating a Handler
@@ -169,4 +194,48 @@ public class LocalRepoService extends Service {
intent.putExtra(STATE, STOPPED);
LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);
}
+
+ private void registerMDNSService() {
+ String repoName = Preferences.get().getLocalRepoName();
+ final HashMap values = new HashMap();
+ values.put("path", "/fdroid/repo");
+ values.put("name", repoName);
+ // TODO set type based on "use HTTPS" pref
+ // values.put("fingerprint", FDroidApp.repo.fingerprint);
+ values.put("type", "fdroidrepo");
+ pairService = ServiceInfo.create("_http._tcp.local.",
+ repoName, FDroidApp.port, 0, 0, values);
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ jmdns = JmDNS.create();
+ jmdns.registerService(pairService);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ }
+
+ private void unregisterMDNSService() {
+ if (localRepoBonjourChangeListener != null) {
+ Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
+ localRepoBonjourChangeListener = null;
+ }
+ if (jmdns != null) {
+ if (pairService != null) {
+ jmdns.unregisterService(pairService);
+ pairService = null;
+ }
+ jmdns.unregisterAllServices();
+ try {
+ jmdns.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ jmdns = null;
+ }
+ }
}
diff --git a/src/org/fdroid/fdroid/net/MDnsHelper.java b/src/org/fdroid/fdroid/net/MDnsHelper.java
new file mode 100644
index 000000000..ab3404138
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/MDnsHelper.java
@@ -0,0 +1,258 @@
+
+package org.fdroid.fdroid.net;
+
+import android.app.Activity;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.MulticastLock;
+import android.os.AsyncTask;
+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.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Enumeration;
+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;
+ private MulticastLock mMulticastLock;
+
+ public MDnsHelper(Activity activity, final RepoScanListAdapter adapter) {
+ mActivity = activity;
+ mAdapter = adapter;
+ WifiManager wm = (WifiManager) activity.getSystemService(Context.WIFI_SERVICE);
+ mMulticastLock = wm.createMulticastLock(activity.getPackageName());
+ mMulticastLock.setReferenceCounted(false);
+ }
+
+ @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 serviceAdded(ServiceEvent event) {
+ addFDroidService(event);
+ }
+
+ @Override
+ public void serviceResolved(ServiceEvent event) {
+ addFDroidService(event);
+ }
+
+ private void addFDroidService(ServiceEvent event) {
+ // a ListView Adapter can only be updated on the UI thread
+ final ServiceInfo serviceInfo = event.getInfo();
+ String type = serviceInfo.getPropertyString("type");
+ if (type.startsWith("fdroidrepo"))
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.addItem(serviceInfo);
+ }
+ });
+ }
+
+ public void discoverServices() {
+ mMulticastLock.acquire();
+ 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() {
+ mMulticastLock.release();
+ 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/net/WifiStateChangeService.java b/src/org/fdroid/fdroid/net/WifiStateChangeService.java
index 38d05740c..78b35abe6 100644
--- a/src/org/fdroid/fdroid/net/WifiStateChangeService.java
+++ b/src/org/fdroid/fdroid/net/WifiStateChangeService.java
@@ -14,6 +14,7 @@ import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import java.util.Locale;
@@ -57,11 +58,13 @@ public class WifiStateChangeService extends Service {
FDroidApp.bssid = wifiInfo.getBSSID();
String scheme;
+ // TODO move this to Preferences.get().isHttpsEnabled();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(WifiStateChangeService.this);
if (prefs.getBoolean("use_https", false))
scheme = "https";
else
scheme = "http";
+ FDroidApp.repo.name = Preferences.get().getLocalRepoName();
FDroidApp.repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo",
scheme, FDroidApp.ipAddressString, FDroidApp.port);
FDroidApp.localRepo.setUriString(FDroidApp.repo.address);
diff --git a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java
index 0fc2b8fa1..d5b23c5ac 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;
@@ -25,28 +17,16 @@ 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 +35,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 +228,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();
-
- String serviceType = serviceInfo.getServiceType();
- String protocol = serviceType.contains("fdroidrepos") ? "https://" : "http://";
-
- String serviceAddress = protocol + serviceInfo.getHost().getHostAddress()
- + ":" + serviceInfo.getPort() + "/fdroid/repo";
- showAddRepo(serviceAddress, "");
- }
+ final ServiceInfo serviceInfo = discoveredService.getServiceInfo();
+ String type = serviceInfo.getPropertyString("type");
+ String protocol = type.contains("fdroidrepos") ? "https:/" : "http:/";
+ String path = serviceInfo.getPropertyString("path");
+ if (TextUtils.isEmpty(path))
+ path = "/fdroid/repo";
+ String serviceUrl = protocol + serviceInfo.getInetAddresses()[0]
+ + ":" + serviceInfo.getPort() + path;
+ // TODO get fingerprint from TXT record
+ showAddRepo(serviceUrl, "");
+ }
});
alrt.show();
-
- Log.d("FDroid", "Starting network service discovery");
- nsdHelper.discoverServices();
+ mDnsHelper.discoverServices();
}
public void importRepo(String uri, String fingerprint) {
@@ -416,7 +397,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();
diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml
index 12eccb755..747886971 100644
--- a/test/AndroidManifest.xml
+++ b/test/AndroidManifest.xml
@@ -10,6 +10,8 @@
+
+