From 54283b71fd331c8c21ea2bb370909b3a269b8627 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 May 2014 13:33:49 -0400 Subject: [PATCH 01/11] add jmdns submodule to Eclipse config So those of us stuck in Eclipse can built FDroid :) --- .classpath | 1 + 1 file changed, 1 insertion(+) diff --git a/.classpath b/.classpath index fd733198e..82469dc51 100644 --- a/.classpath +++ b/.classpath @@ -5,6 +5,7 @@ + From 7ca966be052cb4b5efc59d168febacbdca9609c8 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 May 2014 21:45:12 -0400 Subject: [PATCH 02/11] in local repo, make relative links to the index.html page This should make the local repo files portable, like if the app gets moved to the SD card or things like that. --- src/org/fdroid/fdroid/Utils.java | 2 +- src/org/fdroid/fdroid/localrepo/LocalRepoManager.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java index f071579a5..9b1f29ee3 100644 --- a/src/org/fdroid/fdroid/Utils.java +++ b/src/org/fdroid/fdroid/Utils.java @@ -118,7 +118,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")); diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 5b21235a2..a1d05ba7f 100644 --- a/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -125,10 +125,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 +136,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(); } From d25a395936d4f16a80651f497a996c5c16224f90 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 16 May 2014 16:08:30 -0400 Subject: [PATCH 03/11] broadcast local repo via Bonjour/mDNS to enable auto-discovery This adds support for registering the local repo with Bonjour/mDNS so that it is broadcast out to all devices on the local network. This makes it easy to discover and add local repos on the same wifi. refs #2900 https://dev.guardianproject.info/issues/2900 --- res/values/strings.xml | 5 +- res/xml/preferences.xml | 6 + src/org/fdroid/fdroid/Preferences.java | 19 +++ .../fdroid/fdroid/PreferencesActivity.java | 5 + .../fdroid/localrepo/LocalRepoService.java | 114 ++++++++++++++---- 5 files changed, 124 insertions(+), 25 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index de6073c47..12baeeb47 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -34,7 +34,10 @@ 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 + Advertize your local repo using Bonjour (mDNS) + Do not advertize your local repo. + Search Results App Details No such app found diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index fa534900f..aca5061f6 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -43,6 +43,12 @@ 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 boolean isInitialized(String key) { return initialized.containsKey(key) && initialized.get(key); @@ -74,6 +77,10 @@ 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); + } + public boolean hasCompactLayout() { if (!isInitialized(PREF_COMPACT_LAYOUT)) { initialize(PREF_COMPACT_LAYOUT); @@ -146,6 +153,10 @@ 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(); + } } } @@ -157,6 +168,14 @@ 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 static interface ChangeListener { public void onPreferenceChange(); } diff --git a/src/org/fdroid/fdroid/PreferencesActivity.java b/src/org/fdroid/fdroid/PreferencesActivity.java index 55c36ec3e..2d7d40191 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -54,6 +54,7 @@ public class PreferencesActivity extends PreferenceActivity implements Preferences.PREF_PERMISSIONS, Preferences.PREF_COMPACT_LAYOUT, Preferences.PREF_IGN_TOUCH, + Preferences.PREF_LOCAL_REPO_BONJOUR, Preferences.PREF_CACHE_APK, Preferences.PREF_EXPERT, Preferences.PREF_ROOT_INSTALLER, @@ -146,6 +147,10 @@ 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_CACHE_APK)) { onoffSummary(key, R.string.cache_downloaded_on, R.string.cache_downloaded_off); diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java index c9d6511c6..4ecc8e23e 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(); } }; @@ -88,14 +98,16 @@ public class LocalRepoService extends Service { 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) + 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 +121,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 +132,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 +151,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 +193,46 @@ public class LocalRepoService extends Service { intent.putExtra(STATE, STOPPED); LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent); } + + private void registerMDNSService() { + final HashMap values = new HashMap(); + values.put("path", "/fdroid/repo"); + values.put("name", FDroidApp.repo.name); + // TODO set type based on "use HTTPS" pref + // values.put("fingerprint", FDroidApp.repo.fingerprint); + values.put("type", "fdroidrepo"); + pairService = ServiceInfo.create("_http._tcp.local.", + FDroidApp.repo.name, 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; + } + } } From 248cefe1f35d778f27361bb9a81f63f72deadb83 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 16 May 2014 16:23:54 -0400 Subject: [PATCH 04/11] fix notification launching LocalRepoActivity It was not working on my Nexus 7 running stock 4.4.2. This seems to be the recommend fix: https://code.google.com/p/android/issues/detail?id=61850 --- res/values/strings.xml | 4 ++-- src/org/fdroid/fdroid/localrepo/LocalRepoService.java | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 12baeeb47..c02cf391e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -35,8 +35,8 @@ Use system permissions to install, update, and remove packages Do not use system permissions to install, update, and remove packages Broadcast Local Repo - Advertize your local repo using Bonjour (mDNS) - Do not advertize your local repo. + Advertise your local repo using Bonjour (mDNS) + Do not advertise your local repo. Search Results App Details diff --git a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java index 4ecc8e23e..18571c401 100644 --- a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java +++ b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java @@ -97,7 +97,8 @@ 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); + 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)) @@ -223,9 +224,10 @@ public class LocalRepoService extends Service { localRepoBonjourChangeListener = null; } if (jmdns != null) { - if (pairService != null) + if (pairService != null) { jmdns.unregisterService(pairService); - pairService = null; + pairService = null; + } jmdns.unregisterAllServices(); try { jmdns.close(); From b5f7c0a481fdad000249b00b99fed15b40668f28 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 16 May 2014 17:23:29 -0400 Subject: [PATCH 05/11] preference for the "name" of the local repo This name is used in the RepoList, the local repo website title, the Bonjour broadcast, etc. By default, a name is generated using the make and model of the phone plus a random number. --- res/values/strings.xml | 2 ++ res/xml/preferences.xml | 3 ++ src/org/fdroid/fdroid/Preferences.java | 29 +++++++++++++++++++ .../fdroid/fdroid/PreferencesActivity.java | 11 ++++--- src/org/fdroid/fdroid/Utils.java | 5 ---- .../fdroid/localrepo/LocalRepoManager.java | 3 +- .../fdroid/localrepo/LocalRepoService.java | 5 ++-- .../fdroid/net/WifiStateChangeService.java | 3 ++ 8 files changed, 49 insertions(+), 12 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c02cf391e..5b21f4912 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -37,6 +37,8 @@ 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 diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index aca5061f6..fce6f7cb3 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -48,6 +48,9 @@ android:defaultValue="true" android:key="localRepoBonjour" android:title="@string/local_repo_bonjour" /> + 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); @@ -81,6 +89,15 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi 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); @@ -157,6 +174,10 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi for ( ChangeListener listener : localRepoBonjourListeners ) { listener.onPreferenceChange(); } + } else if (key.equals(PREF_LOCAL_REPO_NAME)) { + for ( ChangeListener listener : localRepoNameListeners ) { + listener.onPreferenceChange(); + } } } @@ -176,6 +197,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi 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 2d7d40191..15e532e4a 100644 --- a/src/org/fdroid/fdroid/PreferencesActivity.java +++ b/src/org/fdroid/fdroid/PreferencesActivity.java @@ -55,6 +55,7 @@ public class PreferencesActivity extends PreferenceActivity implements 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, @@ -88,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) { @@ -118,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, @@ -151,6 +151,9 @@ public class PreferencesActivity extends PreferenceActivity implements 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 9b1f29ee3..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; @@ -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 a1d05ba7f..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; @@ -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 18571c401..690105fdc 100644 --- a/src/org/fdroid/fdroid/localrepo/LocalRepoService.java +++ b/src/org/fdroid/fdroid/localrepo/LocalRepoService.java @@ -196,14 +196,15 @@ public class LocalRepoService extends Service { } private void registerMDNSService() { + String repoName = Preferences.get().getLocalRepoName(); final HashMap values = new HashMap(); values.put("path", "/fdroid/repo"); - values.put("name", FDroidApp.repo.name); + 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.", - FDroidApp.repo.name, FDroidApp.port, 0, 0, values); + repoName, FDroidApp.port, 0, 0, values); new Thread(new Runnable() { @Override 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); From f2994b0764b53c4c103b94196a8719bcc57e6131 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 May 2014 21:49:07 -0400 Subject: [PATCH 06/11] replace Android NSD browsing with jmdns Android's NSD is only included in Android >= 16, and seems to be buggy. So This replaces the NSD code entirely with jmdns, which works on all Android versions. fixes #2900 https://dev.guardianproject.info/issues/2900 fixes #3379 https://dev.guardianproject.info/issues/3379 --- res/layout/repodiscoveryitem.xml | 3 +- res/values/strings.xml | 4 +- src/org/fdroid/fdroid/net/MDnsHelper.java | 249 +++++++++++++++++ src/org/fdroid/fdroid/net/NsdHelper.java | 252 ------------------ .../views/fragments/RepoListFragment.java | 93 +++---- 5 files changed, 288 insertions(+), 313 deletions(-) create mode 100644 src/org/fdroid/fdroid/net/MDnsHelper.java delete mode 100644 src/org/fdroid/fdroid/net/NsdHelper.java 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(); From 30ecc1c9f6d7c26ec8600e53e1f89c4430ea5982 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 May 2014 21:57:39 -0400 Subject: [PATCH 07/11] use MulticastLock for better Bonjour/mDNS performance Multicast transmission is subject to heavy power management on Android, because it apparently can be a battery drain. mDNS/Bonjour is based entirely on multicast, so in order to have good Bonjour performance, there needs to be good multicast performance. MulticastLock provides that. fixes #3381 https://dev.guardianproject.info/issues/3381 --- AndroidManifest.xml | 1 + src/org/fdroid/fdroid/net/MDnsHelper.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 13d8b590d..0c1ecc827 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -34,6 +34,7 @@ + diff --git a/src/org/fdroid/fdroid/net/MDnsHelper.java b/src/org/fdroid/fdroid/net/MDnsHelper.java index 48e2bc853..30a1305a6 100644 --- a/src/org/fdroid/fdroid/net/MDnsHelper.java +++ b/src/org/fdroid/fdroid/net/MDnsHelper.java @@ -3,6 +3,8 @@ 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; @@ -32,10 +34,14 @@ public class MDnsHelper implements ServiceListener { 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 @@ -75,6 +81,7 @@ public class MDnsHelper implements ServiceListener { } public void discoverServices() { + mMulticastLock.acquire(); new AsyncTask() { @Override @@ -98,6 +105,7 @@ public class MDnsHelper implements ServiceListener { } public void stopDiscovery() { + mMulticastLock.release(); if (mJmdns == null) return; mJmdns.removeServiceListener(HTTP_SERVICE_TYPE, MDnsHelper.this); From 8ebce69d5d3b77045284eb92ff7e07b5fce51b97 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 May 2014 22:34:37 -0400 Subject: [PATCH 08/11] use mDNS TXT records to detect FDroid repos, path, etc. FDroid repos are advertised via Bonjour as plain HTTP or HTTPS services, since they are browseable with a standard Web Browser. The "Find Local Repos" browser in FDroid should only show FDroid repos, not any website, so that is detected using a "type" TXT record in the FDroid broadcasts. --- src/org/fdroid/fdroid/net/MDnsHelper.java | 37 ++++++++++--------- .../views/fragments/RepoListFragment.java | 19 +++++----- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/org/fdroid/fdroid/net/MDnsHelper.java b/src/org/fdroid/fdroid/net/MDnsHelper.java index 30a1305a6..ab3404138 100644 --- a/src/org/fdroid/fdroid/net/MDnsHelper.java +++ b/src/org/fdroid/fdroid/net/MDnsHelper.java @@ -8,6 +8,7 @@ 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; @@ -20,6 +21,7 @@ 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.*; @@ -44,18 +46,6 @@ public class MDnsHelper implements ServiceListener { mMulticastLock.setReferenceCounted(false); } - @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 @@ -68,16 +58,27 @@ public class MDnsHelper implements ServiceListener { }); } + @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(); - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mAdapter.addItem(serviceInfo); - } - }); + String type = serviceInfo.getPropertyString("type"); + if (type.startsWith("fdroidrepo")) + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mAdapter.addItem(serviceInfo); + } + }); } public void discoverServices() { diff --git a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java index 7423d7bc2..d5b23c5ac 100644 --- a/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/RepoListFragment.java @@ -14,6 +14,7 @@ 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.*; @@ -263,15 +264,15 @@ public class RepoListFragment extends ListFragment (DiscoveredRepo) parent.getItemAtPosition(position); final ServiceInfo serviceInfo = discoveredService.getServiceInfo(); - - String serviceType = serviceInfo.getType(); - // TODO get repo type from TXT record - String protocol = serviceType.contains("fdroidrepos") ? "https:/" : "http:/"; - - String serviceAddress = protocol + serviceInfo.getInetAddresses()[0] - + ":" + serviceInfo.getPort() + "/fdroid/repo"; - // TODO get path from TXT record - showAddRepo(serviceAddress, ""); + 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, ""); } }); From e782730455e312ca21a07dc123f95bd49c8eb63a Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 May 2014 14:34:46 -0400 Subject: [PATCH 09/11] comment out incomplete, failing tests These are tests that @pserwylo was working on, but hasn't had the time to complete yet. As far as I know, they have never passed. --- test/src/org/fdroid/fdroid/AppProviderTest.java | 11 ++++++----- test/src/org/fdroid/fdroid/InstalledAppCacheTest.java | 11 +++++------ .../org/fdroid/fdroid/InstalledAppProviderTest.java | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/src/org/fdroid/fdroid/AppProviderTest.java b/test/src/org/fdroid/fdroid/AppProviderTest.java index 12124848e..644dbca7d 100644 --- a/test/src/org/fdroid/fdroid/AppProviderTest.java +++ b/test/src/org/fdroid/fdroid/AppProviderTest.java @@ -1,16 +1,14 @@ package org.fdroid.fdroid; -import android.content.ContentResolver; import android.content.ContentValues; import android.content.res.Resources; import android.database.Cursor; + import mock.MockCategoryResources; -import mock.MockContextSwappableComponents; -import mock.MockInstallablePackageManager; + import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.InstalledAppCacheUpdater; import java.util.ArrayList; import java.util.List; @@ -45,6 +43,7 @@ public class AppProviderTest extends FDroidProviderTest { * the AppProvider used to stumble across this bug when asking for installed apps, * and the device had over 1000 apps installed. */ +/* TODO fix me public void testMaxSqliteParams() { MockInstallablePackageManager pm = new MockInstallablePackageManager(); @@ -75,7 +74,7 @@ public class AppProviderTest extends FDroidProviderTest { assertResultCount(3, AppProvider.getInstalledUri()); } - +*/ public void testCantFindApp() { assertNull(AppProvider.Helper.findById(getMockContentResolver(), "com.example.doesnt-exist")); } @@ -106,6 +105,7 @@ public class AppProviderTest extends FDroidProviderTest { assertNotNull(cursor); } +/* TODO fix me private void insertApps(int count) { for (int i = 0; i < count; i ++) { insertApp("com.example.test." + i, "Test app " + i); @@ -246,6 +246,7 @@ public class AppProviderTest extends FDroidProviderTest { assertResultCount(10, AppProvider.getInstalledUri()); } +*/ public void testInsert() { diff --git a/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java b/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java index 2a849da7f..ff512de0b 100644 --- a/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java +++ b/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java @@ -1,15 +1,13 @@ package org.fdroid.fdroid; -import android.database.Cursor; -import android.net.Uri; import mock.MockInstallablePackageManager; -import org.fdroid.fdroid.data.InstalledAppCacheUpdater; + import org.fdroid.fdroid.data.InstalledAppProvider; /** - * Tests the ability of the {@link org.fdroid.fdroid.data.InstalledAppCacheUpdater} to stay in sync with + * Tests the ability of the {@link org.fdroid.fdroid.data.InstalledAppCacheUpdater} to stay in sync with * the {@link android.content.pm.PackageManager}. - * For practical reasons, it extends FDroidProviderTest, although there is also a + * For practical reasons, it extends FDroidProviderTest, although there is also a * separate test for the InstalledAppProvider which tests the CRUD operations in more detail. */ public class InstalledAppCacheTest extends FDroidProviderTest { @@ -42,6 +40,7 @@ public class InstalledAppCacheTest extends FDroidProviderTest { @@ -38,6 +37,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest Date: Mon, 19 May 2014 16:54:59 -0400 Subject: [PATCH 10/11] tests: try everything to find a folder to write to, otherwise skip The tests of the jar signatures require working on files, but some emulators make that very difficult. So try all possible paths for writing to, and otherwise just skip the tests. --- test/AndroidManifest.xml | 2 + .../fdroid/updater/SignedRepoUpdaterTest.java | 59 +++++++++++++------ 2 files changed, 44 insertions(+), 17 deletions(-) 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 @@ + +