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
This commit is contained in:
Hans-Christoph Steiner 2014-05-16 16:08:30 -04:00
parent 7ca966be05
commit d25a395936
5 changed files with 124 additions and 25 deletions

View File

@ -34,7 +34,10 @@
<string name="system_installer">Install using system-permissions</string>
<string name="system_installer_on">Use system permissions to install, update, and remove packages</string>
<string name="system_installer_off">Do not use system permissions to install, update, and remove packages</string>
<string name="local_repo_bonjour">Broadcast Local Repo</string>
<string name="local_repo_bonjour_on">Advertize your local repo using Bonjour (mDNS)</string>
<string name="local_repo_bonjour_off">Do not advertize your local repo.</string>
<string name="search_results">Search Results</string>
<string name="app_details">App Details</string>
<string name="no_such_app">No such app found</string>

View File

@ -43,6 +43,12 @@
android:defaultValue="false"
android:key="ignoreTouchscreen" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/local_repo">
<CheckBoxPreference
android:defaultValue="true"
android:key="localRepoBonjour"
android:title="@string/local_repo_bonjour" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/other">
<CheckBoxPreference android:title="@string/cache_downloaded"
android:defaultValue="false"

View File

@ -38,12 +38,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
public static final String PREF_UPD_LAST = "lastUpdateCheck";
public static final String PREF_ROOT_INSTALLER = "rootInstaller";
public static final String PREF_SYSTEM_INSTALLER = "systemInstaller";
public static final String PREF_LOCAL_REPO_BONJOUR = "localRepoBonjour";
private static final boolean DEFAULT_COMPACT_LAYOUT = false;
private static final boolean DEFAULT_ROOTED = true;
private static final int DEFAULT_UPD_HISTORY = 14;
private static final boolean DEFAULT_ROOT_INSTALLER = false;
private static final boolean DEFAULT_SYSTEM_INSTALLER = false;
private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
private boolean compactLayout = DEFAULT_COMPACT_LAYOUT;
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
@ -53,6 +55,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
private List<ChangeListener> compactLayoutListeners = new ArrayList<ChangeListener>();
private List<ChangeListener> filterAppsRequiringRootListeners = new ArrayList<ChangeListener>();
private List<ChangeListener> updateHistoryListeners = new ArrayList<ChangeListener>();
private List<ChangeListener> localRepoBonjourListeners = new ArrayList<ChangeListener>();
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();
}

View File

@ -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);

View File

@ -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<String, String> values = new HashMap<String, String>();
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;
}
}
}