diff --git a/app/build.gradle b/app/build.gradle index 2b7d709d6..47fa26d11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -153,7 +153,6 @@ dependencies { implementation 'commons-net:commons-net:3.6' implementation 'ch.acra:acra:4.9.1' implementation 'io.reactivex:rxjava:1.1.0' - implementation 'io.reactivex:rxandroid:0.23.0' implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1' implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5' diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/BluetoothManager.java b/app/src/full/java/org/fdroid/fdroid/localrepo/BluetoothManager.java index b7f8de854..d6b3cb41c 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/BluetoothManager.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/BluetoothManager.java @@ -1,8 +1,11 @@ package org.fdroid.fdroid.localrepo; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -11,6 +14,8 @@ import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.localrepo.peers.BluetoothPeer; import org.fdroid.fdroid.net.bluetooth.BluetoothServer; import java.lang.ref.WeakReference; @@ -26,6 +31,9 @@ import java.lang.ref.WeakReference; public class BluetoothManager { private static final String TAG = "BluetoothManager"; + public static final String ACTION_FOUND = "BluetoothNewPeer"; + public static final String EXTRA_PEER = "extraBluetoothPeer"; + public static final String ACTION_STATUS = "BluetoothStatus"; public static final String EXTRA_STATUS = "BluetoothStatusExtra"; public static final int STATUS_STARTING = 0; @@ -74,6 +82,9 @@ public class BluetoothManager { handlerThread = new HandlerThread("BluetoothManager", Process.THREAD_PRIORITY_LESS_FAVORABLE) { @Override protected void onLooperPrepared() { + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + localBroadcastManager.registerReceiver(bluetoothDeviceFound, + new IntentFilter(BluetoothDevice.ACTION_FOUND)); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (!bluetoothAdapter.enable()) { sendBroadcast(STATUS_ERROR, context.getString(R.string.swap_error_cannot_start_bluetooth)); @@ -85,12 +96,17 @@ public class BluetoothManager { } else { sendBroadcast(STATUS_ERROR, context.getString(R.string.swap_error_cannot_start_bluetooth)); } + for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { + sendFoundBroadcast(context, device); + } } }; handlerThread.start(); handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + localBroadcastManager.unregisterReceiver(bluetoothDeviceFound); bluetoothServer.close(); if (bluetoothAdapter != null) { bluetoothAdapter.cancelDiscovery(); @@ -132,4 +148,23 @@ public class BluetoothManager { } LocalBroadcastManager.getInstance(context.get()).sendBroadcast(intent); } + + private static final BroadcastReceiver bluetoothDeviceFound = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + sendFoundBroadcast(context, (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); + } + }; + + private static void sendFoundBroadcast(Context context, BluetoothDevice device) { + BluetoothPeer bluetoothPeer = BluetoothPeer.getInstance(device); + if (bluetoothPeer == null) { + Utils.debugLog(TAG, "IGNORING: " + device); + return; + } + Intent intent = new Intent(ACTION_FOUND); + intent.putExtra(EXTRA_PEER, bluetoothPeer); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } } diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/BonjourManager.java b/app/src/full/java/org/fdroid/fdroid/localrepo/BonjourManager.java index f9b581e1e..e579a65c2 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/BonjourManager.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/BonjourManager.java @@ -2,6 +2,7 @@ package org.fdroid.fdroid.localrepo; import android.content.Context; import android.content.Intent; +import android.net.wifi.WifiManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -12,6 +13,7 @@ import android.util.Log; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.localrepo.peers.BonjourPeer; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; @@ -30,9 +32,9 @@ import java.util.HashMap; public class BonjourManager { private static final String TAG = "BonjourManager"; - public static final String ACTION_ADDED = "BonjourAdded"; - public static final String ACTION_RESOLVED = "BonjourResolved"; - public static final String ACTION_REMOVED = "BonjourRemoved"; + public static final String ACTION_FOUND = "BonjourNewPeer"; + public static final String ACTION_REMOVED = "BonjourPeerRemoved"; + public static final String EXTRA_BONJOUR_PEER = "extraBonjourPeer"; public static final String ACTION_STATUS = "BonjourStatus"; public static final String EXTRA_STATUS = "BonjourStatusExtra"; @@ -56,6 +58,7 @@ public class BonjourManager { private static volatile HandlerThread handlerThread; private static ServiceInfo pairService; private static JmDNS jmdns; + private static WifiManager.MulticastLock multicastLock; public static boolean isAlive() { return handlerThread != null && handlerThread.isAlive(); @@ -116,6 +119,8 @@ public class BonjourManager { } sendBroadcast(STATUS_STARTING, null); + final WifiManager wifiManager = (WifiManager) context.getApplicationContext() + .getSystemService(Context.WIFI_SERVICE); handlerThread = new HandlerThread("BonjourManager", Process.THREAD_PRIORITY_LESS_FAVORABLE) { @Override protected void onLooperPrepared() { @@ -124,6 +129,11 @@ public class BonjourManager { jmdns = JmDNS.create(address); jmdns.addServiceListener(HTTP_SERVICE_TYPE, httpServiceListener); jmdns.addServiceListener(HTTPS_SERVICE_TYPE, httpsServiceListener); + + multicastLock = wifiManager.createMulticastLock(context.getPackageName()); + multicastLock.setReferenceCounted(false); + multicastLock.acquire(); + sendBroadcast(STATUS_STARTED, null); } catch (IOException e) { if (handler != null) { @@ -155,15 +165,15 @@ public class BonjourManager { private void handleVisible(String localRepoName, boolean useHttps) { HashMap values = new HashMap<>(); - values.put("path", "/fdroid/repo"); - values.put("name", localRepoName); - values.put("fingerprint", FDroidApp.repo.fingerprint); + values.put(BonjourPeer.PATH, "/fdroid/repo"); + values.put(BonjourPeer.NAME, localRepoName); + values.put(BonjourPeer.FINGERPRINT, FDroidApp.repo.fingerprint); String type; if (useHttps) { - values.put("type", "fdroidrepos"); + values.put(BonjourPeer.TYPE, "fdroidrepos"); type = HTTPS_SERVICE_TYPE; } else { - values.put("type", "fdroidrepo"); + values.put(BonjourPeer.TYPE, "fdroidrepo"); type = HTTP_SERVICE_TYPE; } ServiceInfo newPairService = ServiceInfo.create(type, localRepoName, FDroidApp.port, 0, 0, values); @@ -190,6 +200,9 @@ public class BonjourManager { } private void handleStop() { + if (multicastLock != null) { + multicastLock.release(); + } if (jmdns != null) { jmdns.unregisterAllServices(); Utils.closeQuietly(jmdns); @@ -228,9 +241,14 @@ public class BonjourManager { start(context, localRepoName, useHttps, httpServiceListener, httpsServiceListener); } - private static void sendBroadcast(String action, String message) { + private static void sendBroadcast(String action, ServiceInfo serviceInfo) { + BonjourPeer bonjourPeer = BonjourPeer.getInstance(serviceInfo); + if (bonjourPeer == null) { + Utils.debugLog(TAG, "IGNORING: " + serviceInfo); + return; + } Intent intent = new Intent(action); - intent.putExtra(Intent.EXTRA_TEXT, message); + intent.putExtra(EXTRA_BONJOUR_PEER, bonjourPeer); LocalBroadcastManager.getInstance(context.get()).sendBroadcast(intent); } @@ -250,20 +268,17 @@ public class BonjourManager { private static class SwapServiceListener implements ServiceListener { @Override public void serviceAdded(ServiceEvent serviceEvent) { - Utils.debugLog(TAG, "Service added: " + serviceEvent.getInfo()); - sendBroadcast(ACTION_ADDED, serviceEvent.getInfo().toString()); + // ignored, we only need resolved info } @Override public void serviceRemoved(ServiceEvent serviceEvent) { - Utils.debugLog(TAG, "Service removed: " + serviceEvent.getInfo()); - sendBroadcast(ACTION_REMOVED, serviceEvent.getInfo().toString()); + sendBroadcast(ACTION_REMOVED, serviceEvent.getInfo()); } @Override public void serviceResolved(ServiceEvent serviceEvent) { - Utils.debugLog(TAG, "Service resolved: " + serviceEvent.getInfo()); - sendBroadcast(ACTION_RESOLVED, serviceEvent.getInfo().toString()); + sendBroadcast(ACTION_FOUND, serviceEvent.getInfo()); } } } diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java index e6e8a0b2e..610aa9afa 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -31,14 +31,9 @@ import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.localrepo.peers.Peer; -import org.fdroid.fdroid.localrepo.peers.PeerFinder; import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; import java.io.IOException; import java.io.OutputStream; @@ -55,9 +50,7 @@ import java.util.TimerTask; * Central service which manages all of the different moving parts of swap which are required * to enable p2p swapping of apps. */ -@SuppressWarnings("LineLength") public class SwapService extends Service { - private static final String TAG = "SwapService"; private static final String SHARED_PREFERENCES = "swap-state"; @@ -69,6 +62,7 @@ public class SwapService extends Service { @NonNull private final Set appsToSwap = new HashSet<>(); + private final Set activePeers = new HashSet<>(); private static LocalBroadcastManager localBroadcastManager; private static SharedPreferences swapPreferences; @@ -90,41 +84,16 @@ public class SwapService extends Service { context.stopService(intent); } - /** - * Search for peers to swap - */ - private Observable peerFinder; - - /** - * Call {@link Observable#subscribe()} on this in order to be notified of peers - * which are found. Call {@link Subscription#unsubscribe()} on the resulting - * subscription when finished and you no longer want to scan for peers. - *

- * The returned object will scan for peers on a background thread, and emit - * found peers on the mian thread. - *

- * Invoking this in multiple places will return the same, cached, peer finder. - * That is, if in the past it already found some peers, then you subscribe - * to it in the future, the future subscriber will still receive the peers - * that were found previously. - * TODO: What about removing peers that no longer are present? - */ - public Observable scanForPeers() { - Utils.debugLog(TAG, "Scanning for nearby devices to swap with..."); - if (peerFinder == null) { - peerFinder = PeerFinder.createObservable(getApplicationContext()) - .subscribeOn(Schedulers.newThread()) - .observeOn(AndroidSchedulers.mainThread()) - .distinct(); - } - return peerFinder; - } - @NonNull public Set getAppsToSwap() { return appsToSwap; } + @NonNull + public Set getActivePeers() { + return activePeers; + } + public void connectToPeer() { if (getPeer() == null) { throw new IllegalStateException("Cannot connect to peer, no peer has been selected."); @@ -387,6 +356,9 @@ public class SwapService extends Service { localBroadcastManager.registerReceiver(onWifiChange, new IntentFilter(WifiStateChangeService.BROADCAST)); localBroadcastManager.registerReceiver(bluetoothStatus, new IntentFilter(BluetoothManager.ACTION_STATUS)); + localBroadcastManager.registerReceiver(bluetoothPeerFound, new IntentFilter(BluetoothManager.ACTION_FOUND)); + localBroadcastManager.registerReceiver(bonjourPeerFound, new IntentFilter(BonjourManager.ACTION_FOUND)); + localBroadcastManager.registerReceiver(bonjourPeerRemoved, new IntentFilter(BonjourManager.ACTION_REMOVED)); localBroadcastManager.registerReceiver(localRepoStatus, new IntentFilter(LocalRepoService.ACTION_STATUS)); BonjourManager.start(this); @@ -418,6 +390,9 @@ public class SwapService extends Service { Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener); localBroadcastManager.unregisterReceiver(onWifiChange); localBroadcastManager.unregisterReceiver(bluetoothStatus); + localBroadcastManager.unregisterReceiver(bluetoothPeerFound); + localBroadcastManager.unregisterReceiver(bonjourPeerFound); + localBroadcastManager.unregisterReceiver(bonjourPeerRemoved); unregisterReceiver(bluetoothScanModeChanged); @@ -597,4 +572,25 @@ public class SwapService extends Service { } } }; + + private final BroadcastReceiver bluetoothPeerFound = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + activePeers.add((Peer) intent.getParcelableExtra(BluetoothManager.EXTRA_PEER)); + } + }; + + private final BroadcastReceiver bonjourPeerFound = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + activePeers.add((Peer) intent.getParcelableExtra(BonjourManager.EXTRA_BONJOUR_PEER)); + } + }; + + private final BroadcastReceiver bonjourPeerRemoved = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + activePeers.remove((Peer) intent.getParcelableExtra(BonjourManager.EXTRA_BONJOUR_PEER)); + } + }; } \ No newline at end of file diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothFinder.java b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothFinder.java deleted file mode 100644 index 8c024e3a5..000000000 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothFinder.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.fdroid.fdroid.localrepo.peers; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothDevice; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.Log; - -import org.fdroid.fdroid.Utils; - -import rx.Observable; -import rx.Subscriber; -import rx.functions.Action0; -import rx.subscriptions.Subscriptions; - -@SuppressWarnings("LineLength") -final class BluetoothFinder extends PeerFinder { - - public static Observable createBluetoothObservable(final Context context) { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - final BluetoothFinder finder = new BluetoothFinder(context, subscriber); - - subscriber.add(Subscriptions.create(new Action0() { - @Override - public void call() { - finder.cancel(); - } - })); - - finder.scan(); - } - }); - } - - private static final String TAG = "BluetoothFinder"; - - private final BluetoothAdapter adapter; - - private BluetoothFinder(Context context, Subscriber subscriber) { - super(context, subscriber); - adapter = BluetoothAdapter.getDefaultAdapter(); - } - - private BroadcastReceiver deviceFoundReceiver; - private BroadcastReceiver scanCompleteReceiver; - - private void scan() { - - if (adapter == null) { - Log.i(TAG, "Not scanning for bluetooth peers to swap with, couldn't find a bluetooth adapter on this device."); - return; - } - - isScanning = true; - - if (deviceFoundReceiver == null) { - deviceFoundReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - onDeviceFound(device); - } - } - }; - context.registerReceiver(deviceFoundReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND)); - } - - if (scanCompleteReceiver == null) { - scanCompleteReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (isScanning) { - Utils.debugLog(TAG, "Scan complete, but we haven't been asked to stop scanning yet, so will restart scan."); - startDiscovery(); - } - } - }; - - // TODO: Unregister this receiver at the appropriate time. - context.registerReceiver(scanCompleteReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)); - } - - startDiscovery(); - } - - private void startDiscovery() { - - if (adapter.isDiscovering()) { - // TODO: Can we reset the discovering timeout, so that it doesn't, e.g. time out in 3 - // seconds because we had already almost completed the previous scan? We could - // cancelDiscovery(), but then it will probably prompt the user again. - Utils.debugLog(TAG, "Requested bluetooth scan when already scanning, so will ignore request."); - return; - } - - if (!adapter.startDiscovery()) { - Log.e(TAG, "Couldn't start bluetooth scanning."); - } - - } - - private void cancel() { - if (adapter != null) { - Utils.debugLog(TAG, "Stopping bluetooth discovery."); - adapter.cancelDiscovery(); - } - - isScanning = false; - } - - private void onDeviceFound(BluetoothDevice device) { - if (device != null && device.getName() != null && - (device.getBluetoothClass().getDeviceClass() == BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA || - device.getBluetoothClass().getDeviceClass() == BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA || - device.getBluetoothClass().getDeviceClass() == BluetoothClass.Device.PHONE_SMART)) { - subscriber.onNext(new BluetoothPeer(device)); - } - } - -} diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothPeer.java b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothPeer.java index 6f98626c2..42b9c1153 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothPeer.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BluetoothPeer.java @@ -1,7 +1,9 @@ package org.fdroid.fdroid.localrepo.peers; +import android.bluetooth.BluetoothClass.Device; import android.bluetooth.BluetoothDevice; import android.os.Parcel; +import android.support.annotation.Nullable; import android.text.TextUtils; import org.fdroid.fdroid.R; @@ -11,7 +13,22 @@ public class BluetoothPeer implements Peer { private final BluetoothDevice device; - public BluetoothPeer(BluetoothDevice device) { + /** + * Return a instance if the {@link BluetoothDevice} is a device that could + * host a swap repo. + */ + @Nullable + public static BluetoothPeer getInstance(@Nullable BluetoothDevice device) { + if (device != null && device.getName() != null && + (device.getBluetoothClass().getDeviceClass() == Device.COMPUTER_HANDHELD_PC_PDA + || device.getBluetoothClass().getDeviceClass() == Device.COMPUTER_PALM_SIZE_PC_PDA + || device.getBluetoothClass().getDeviceClass() == Device.PHONE_SMART)) { + return new BluetoothPeer(device); + } + return null; + } + + private BluetoothPeer(BluetoothDevice device) { this.device = device; } diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourFinder.java b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourFinder.java deleted file mode 100644 index 1f0ff4f5a..000000000 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourFinder.java +++ /dev/null @@ -1,162 +0,0 @@ -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 rx.Observable; -import rx.Subscriber; -import rx.functions.Action0; -import rx.subscriptions.Subscriptions; - -import javax.jmdns.JmDNS; -import javax.jmdns.ServiceEvent; -import javax.jmdns.ServiceInfo; -import javax.jmdns.ServiceListener; -import java.io.IOException; -import java.net.InetAddress; - -import static org.fdroid.fdroid.localrepo.BonjourManager.HTTPS_SERVICE_TYPE; -import static org.fdroid.fdroid.localrepo.BonjourManager.HTTP_SERVICE_TYPE; - -@SuppressWarnings("LineLength") -final class BonjourFinder extends PeerFinder implements ServiceListener { - - public static Observable createBonjourObservable(final Context context) { - return Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber 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 JmDNS jmdns; - private WifiManager wifiManager; - private WifiManager.MulticastLock multicastLock; - - private BonjourFinder(Context context, Subscriber subscriber) { - super(context, subscriber); - } - - private void scan() { - - Utils.debugLog(TAG, "Requested Bonjour (mDNS) scan for peers."); - - if (wifiManager == null) { - wifiManager = (WifiManager) context.getApplicationContext().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; - - } - -} diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java index cc836efa9..b40809dfa 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java @@ -2,16 +2,39 @@ package org.fdroid.fdroid.localrepo.peers; import android.net.Uri; import android.os.Parcel; +import android.support.annotation.Nullable; import android.text.TextUtils; +import org.fdroid.fdroid.FDroidApp; import javax.jmdns.ServiceInfo; import javax.jmdns.impl.FDroidServiceInfo; public class BonjourPeer extends WifiPeer { + private static final String TAG = "BonjourPeer"; + + public static final String FINGERPRINT = "fingerprint"; + public static final String NAME = "name"; + public static final String PATH = "path"; + public static final String TYPE = "type"; private final FDroidServiceInfo serviceInfo; - public BonjourPeer(ServiceInfo serviceInfo) { + /** + * Return a instance if the {@link ServiceInfo} is fully resolved and does + * not represent this device, but something else on the network. + */ + @Nullable + public static BonjourPeer getInstance(ServiceInfo serviceInfo) { + String type = serviceInfo.getPropertyString(TYPE); + String fingerprint = serviceInfo.getPropertyString(FINGERPRINT); + if (type == null || !type.startsWith("fdroidrepo") + || TextUtils.equals(FDroidApp.repo.fingerprint, fingerprint)) { + return null; + } + return new BonjourPeer(serviceInfo); + } + + private BonjourPeer(ServiceInfo serviceInfo) { this.serviceInfo = new FDroidServiceInfo(serviceInfo); this.name = serviceInfo.getDomain(); this.uri = Uri.parse(this.serviceInfo.getRepoAddress()); diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/Peer.java b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/Peer.java index c1481e297..14a45db29 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/Peer.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/Peer.java @@ -3,11 +3,20 @@ package org.fdroid.fdroid.localrepo.peers; import android.os.Parcelable; import android.support.annotation.DrawableRes; +/** + * TODO This model assumes that "peers" from Bluetooth, Bonjour, and WiFi are + * different things. They are not different repos though, they all point to + * the same repos. This should really be combined to be a single "RemoteRepo" + * class that represents a single device's local repo, and can have zero to + * many ways to connect to it (e.g. Bluetooth, WiFi, USB Thumb Drive, SD Card, + * WiFi Direct, etc). + */ public interface Peer extends Parcelable { String getName(); - @DrawableRes int getIcon(); + @DrawableRes + int getIcon(); boolean equals(Object peer); diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/PeerFinder.java b/app/src/full/java/org/fdroid/fdroid/localrepo/peers/PeerFinder.java deleted file mode 100644 index 6cbffe4bc..000000000 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/peers/PeerFinder.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.fdroid.fdroid.localrepo.peers; - -import android.content.Context; - -import rx.Observable; -import rx.Subscriber; -import rx.schedulers.Schedulers; - -/** - * Searches for other devices in the vicinity, using specific technologies. - * Once found, emits a {@link Peer} to interested {@link Subscriber}s. - */ -public abstract class PeerFinder { - - protected boolean isScanning; - protected final Context context; - protected final Subscriber subscriber; - - protected PeerFinder(Context context, Subscriber subscriber) { - this.context = context; - this.subscriber = subscriber; - } - - public static Observable createObservable(final Context context) { - return Observable.merge( - BluetoothFinder.createBluetoothObservable(context).subscribeOn(Schedulers.newThread()), - BonjourFinder.createBonjourObservable(context).subscribeOn(Schedulers.newThread()) - ); - } - -} diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java index 54fc4fff1..1da4701e3 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -31,8 +31,6 @@ import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.localrepo.SwapView; import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.net.WifiStateChangeService; -import rx.Subscriber; -import rx.Subscription; import java.util.ArrayList; @@ -57,7 +55,7 @@ public class StartSwapView extends SwapView { super(context, attrs, defStyleAttr, defStyleRes); } - private class PeopleNearbyAdapter extends ArrayAdapter { + class PeopleNearbyAdapter extends ArrayAdapter { PeopleNearbyAdapter(Context context) { super(context, 0, new ArrayList()); @@ -93,45 +91,14 @@ public class StartSwapView extends SwapView { private PeopleNearbyAdapter peopleNearbyAdapter; - /** - * When peers are emitted by the peer finder, add them to the adapter - * so that they will show up in the list of peers. - */ - private final Subscriber onPeerFound = new Subscriber() { - - @Override - public void onCompleted() { - uiShowNotSearchingForPeers(); - } - - @Override - public void onError(Throwable e) { - uiShowNotSearchingForPeers(); - } - - @Override - public void onNext(Peer peer) { - Utils.debugLog(TAG, "Found peer: " + peer + ", adding to list of peers in UI."); - peopleNearbyAdapter.add(peer); - } - }; - - private Subscription peerFinderSubscription; - /** * Remove relevant listeners/subscriptions/etc so that they do not receive and process events * when this view is not in use. *

- * TODO: Not sure if this is the best place to handle being removed from the view. */ @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (peerFinderSubscription != null) { - peerFinderSubscription.unsubscribe(); - peerFinderSubscription = null; - } - if (bluetoothSwitch != null) { bluetoothSwitch.setOnCheckedChangeListener(null); } @@ -143,10 +110,6 @@ public class StartSwapView extends SwapView { protected void onFinishInflate() { super.onFinishInflate(); - if (peerFinderSubscription == null) { - peerFinderSubscription = getActivity().getSwapService().scanForPeers().subscribe(onPeerFound); - } - uiInitPeers(); uiInitBluetooth(); uiInitWifi(); @@ -184,6 +147,7 @@ public class StartSwapView extends SwapView { peopleNearbyAdapter = new PeopleNearbyAdapter(getContext()); peopleNearbyList.setAdapter(peopleNearbyAdapter); + peopleNearbyAdapter.addAll(getActivity().getSwapService().getActivePeers()); peopleNearbyList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 79c238df7..beda9e9bf 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -36,10 +36,12 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; +import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -345,7 +347,10 @@ public class SwapWorkflowActivity extends AppCompatActivity { localBroadcastManager.registerReceiver(localRepoStatus, new IntentFilter(LocalRepoService.ACTION_STATUS)); localBroadcastManager.registerReceiver(repoUpdateReceiver, new IntentFilter(UpdateService.LOCAL_ACTION_STATUS)); + localBroadcastManager.registerReceiver(bonjourFound, new IntentFilter(BonjourManager.ACTION_FOUND)); + localBroadcastManager.registerReceiver(bonjourRemoved, new IntentFilter(BonjourManager.ACTION_REMOVED)); localBroadcastManager.registerReceiver(bonjourStatus, new IntentFilter(BonjourManager.ACTION_STATUS)); + localBroadcastManager.registerReceiver(bluetoothFound, new IntentFilter(BluetoothManager.ACTION_FOUND)); localBroadcastManager.registerReceiver(bluetoothStatus, new IntentFilter(BluetoothManager.ACTION_STATUS)); registerReceiver(bluetoothScanModeChanged, @@ -368,7 +373,10 @@ public class SwapWorkflowActivity extends AppCompatActivity { localBroadcastManager.unregisterReceiver(onWifiStateChanged); localBroadcastManager.unregisterReceiver(localRepoStatus); localBroadcastManager.unregisterReceiver(repoUpdateReceiver); + localBroadcastManager.unregisterReceiver(bonjourFound); + localBroadcastManager.unregisterReceiver(bonjourRemoved); localBroadcastManager.unregisterReceiver(bonjourStatus); + localBroadcastManager.unregisterReceiver(bluetoothFound); localBroadcastManager.unregisterReceiver(bluetoothStatus); } @@ -1060,6 +1068,28 @@ public class SwapWorkflowActivity extends AppCompatActivity { } }; + private final BroadcastReceiver bonjourFound = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ListView peopleNearbyList = container.findViewById(R.id.list_people_nearby); + if (peopleNearbyList != null) { + ArrayAdapter peopleNearbyAdapter = (ArrayAdapter) peopleNearbyList.getAdapter(); + peopleNearbyAdapter.add((Peer) intent.getParcelableExtra(BonjourManager.EXTRA_BONJOUR_PEER)); + } + } + }; + + private final BroadcastReceiver bonjourRemoved = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ListView peopleNearbyList = container.findViewById(R.id.list_people_nearby); + if (peopleNearbyList != null) { + ArrayAdapter peopleNearbyAdapter = (ArrayAdapter) peopleNearbyList.getAdapter(); + peopleNearbyAdapter.remove((Peer) intent.getParcelableExtra(BonjourManager.EXTRA_BONJOUR_PEER)); + } + } + }; + private final BroadcastReceiver bluetoothStatus = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -1132,7 +1162,17 @@ public class SwapWorkflowActivity extends AppCompatActivity { default: throw new IllegalArgumentException("Bad intent: " + intent); } + } + }; + private final BroadcastReceiver bluetoothFound = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ListView peopleNearbyList = container.findViewById(R.id.list_people_nearby); + if (peopleNearbyList != null) { + ArrayAdapter peopleNearbyAdapter = (ArrayAdapter) peopleNearbyList.getAdapter(); + peopleNearbyAdapter.add((Peer) intent.getParcelableExtra(BluetoothManager.EXTRA_PEER)); + } } };