package org.fdroid.fdroid.localrepo.peers;
import android.content.Context;
import android.net.wifi.WifiManager;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import java.io.IOException;
import java.net.InetAddress;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
final class BonjourFinder extends PeerFinder implements ServiceListener {
public static Observable<Peer> createBonjourObservable(final Context context) {
return Observable.create(new Observable.OnSubscribe<Peer>() {
@Override
public void call(Subscriber<? super Peer> subscriber) {
final BonjourFinder finder = new BonjourFinder(context, subscriber);
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
finder.cancel();
}
}));
finder.scan();
}
});
}
private static final String TAG = "BonjourFinder";
private static final String HTTP_SERVICE_TYPE = "_http._tcp.local.";
private static final String HTTPS_SERVICE_TYPE = "_https._tcp.local.";
private JmDNS jmdns;
private WifiManager wifiManager;
private WifiManager.MulticastLock multicastLock;
private BonjourFinder(Context context, Subscriber<? super Peer> subscriber) {
super(context, subscriber);
}
private void scan() {
Utils.debugLog(TAG, "Requested Bonjour (mDNS) scan for peers.");
if (wifiManager == null) {
wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
multicastLock = wifiManager.createMulticastLock(context.getPackageName());
multicastLock.setReferenceCounted(false);
}
if (isScanning) {
Utils.debugLog(TAG, "Requested Bonjour scan, but already scanning. But we will still try to explicitly scan for services.");
return;
}
isScanning = true;
multicastLock.acquire();
try {
Utils.debugLog(TAG, "Searching for Bonjour (mDNS) clients...");
jmdns = JmDNS.create(InetAddress.getByName(FDroidApp.ipAddressString));
} catch (IOException e) {
subscriber.onError(e);
return;
}
Utils.debugLog(TAG, "Adding mDNS service listeners for " + HTTP_SERVICE_TYPE + " and " + HTTPS_SERVICE_TYPE);
jmdns.addServiceListener(HTTP_SERVICE_TYPE, this);
jmdns.addServiceListener(HTTPS_SERVICE_TYPE, this);
listServices();
}
private void listServices() {
Utils.debugLog(TAG, "Explicitly querying for services, in addition to waiting for notifications.");
addFDroidServices(jmdns.list(HTTP_SERVICE_TYPE));
addFDroidServices(jmdns.list(HTTPS_SERVICE_TYPE));
}
@Override
public void serviceRemoved(ServiceEvent event) {
}
@Override
public void serviceAdded(final ServiceEvent event) {
// TODO: Get clarification, but it looks like this is:
// 1) Identifying that there is _a_ bonjour service available
// 2) Adding it to the list to give some sort of feedback to the user
// 3) Requesting more detailed info in an async manner
// 4) If that is in fact an fdroid repo (after requesting info), then add it again
// so that more detailed info can be shown to the user.
//
// If so, when is the old one removed?
addFDroidService(event.getInfo());
Utils.debugLog(TAG, "Found JmDNS service, now requesting further details of service");
jmdns.requestServiceInfo(event.getType(), event.getName(), true);
}
@Override
public void serviceResolved(ServiceEvent event) {
addFDroidService(event.getInfo());
}
private void addFDroidServices(ServiceInfo[] services) {
for (ServiceInfo info : services) {
addFDroidService(info);
}
}
/**
* Broadcasts the fact that a Bonjour peer was found to swap with.
* Checks that the service is an F-Droid service, and also that it is not the F-Droid service
* for this device (by comparing its signing fingerprint to our signing fingerprint).
*/
private void addFDroidService(ServiceInfo serviceInfo) {
final String type = serviceInfo.getPropertyString("type");
final String fingerprint = serviceInfo.getPropertyString("fingerprint");
final boolean isFDroid = type != null && type.startsWith("fdroidrepo");
final boolean isSelf = FDroidApp.repo != null && fingerprint != null && fingerprint.equalsIgnoreCase(FDroidApp.repo.fingerprint);
if (isFDroid && !isSelf) {
Utils.debugLog(TAG, "Found F-Droid swap Bonjour service:\n" + serviceInfo);
subscriber.onNext(new BonjourPeer(serviceInfo));
} else {
if (isSelf) {
Utils.debugLog(TAG, "Ignoring Bonjour service because it belongs to this device:\n" + serviceInfo);
} else {
Utils.debugLog(TAG, "Ignoring Bonjour service because it doesn't look like an F-Droid swap repo:\n" + serviceInfo);
}
}
}
private void cancel() {
Utils.debugLog(TAG, "Cancelling BonjourFinder, releasing multicast lock, removing jmdns service listeners");
if (multicastLock != null) {
multicastLock.release();
}
isScanning = false;
if (jmdns == null) {
return;
}
jmdns.removeServiceListener(HTTP_SERVICE_TYPE, this);
jmdns.removeServiceListener(HTTPS_SERVICE_TYPE, this);
jmdns = null;
}
}