diff --git a/F-Droid/res/values/strings.xml b/F-Droid/res/values/strings.xml index 861112c1c..bd55734cb 100644 --- a/F-Droid/res/values/strings.xml +++ b/F-Droid/res/values/strings.xml @@ -322,6 +322,7 @@ Not visible via Bluetooth Visible via Wi-Fi Setting up Wi-Fi… + Stopping Wi-Fi… Not visible via Wi-Fi Device Name Can\'t find who you\'re looking for? diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java index 0dc8380c3..7203aefa5 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java @@ -441,14 +441,18 @@ public class SwapService extends Service { /** * Handles checking if the {@link SwapService} is running, and only restarts it if it was running. */ - public void restartWifiIfEnabled() { + public void stopWifiIfEnabled(final boolean restartAfterStopping) { if (wifiSwap.isConnected()) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - Utils.debugLog(TAG, "Restarting WiFi swap service"); + Utils.debugLog(TAG, "Stopping the currently running WiFi swap service (on background thread)"); wifiSwap.stop(); - wifiSwap.start(); + + if (restartAfterStopping) { + Utils.debugLog(TAG, "Restarting WiFi swap service after stopping (still on background thread)"); + wifiSwap.start(); + } return null; } }.execute(); @@ -483,6 +487,7 @@ public class SwapService extends Service { public static final String WIFI_STATE_CHANGE = "org.fdroid.fdroid.WIFI_STATE_CHANGE"; public static final String EXTRA_STARTING = "STARTING"; public static final String EXTRA_STARTED = "STARTED"; + public static final String EXTRA_STOPPING = "STOPPING"; public static final String EXTRA_STOPPED = "STOPPED"; private static final int NOTIFICATION = 1; @@ -632,7 +637,7 @@ public class SwapService extends Service { @Override public void onPreferenceChange() { Log.i(TAG, "Swap over HTTPS preference changed."); - restartWifiIfEnabled(); + stopWifiIfEnabled(true); } }; @@ -640,7 +645,8 @@ public class SwapService extends Service { private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { - restartWifiIfEnabled(); + boolean hasIp = FDroidApp.ipAddressString != null; + stopWifiIfEnabled(hasIp); } }; diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourBroadcast.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourBroadcast.java index a5ab3a6b9..11711ee49 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourBroadcast.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourBroadcast.java @@ -1,6 +1,7 @@ package org.fdroid.fdroid.localrepo.type; import android.content.Context; +import android.support.annotation.Nullable; import android.util.Log; import org.fdroid.fdroid.FDroidApp; @@ -10,6 +11,7 @@ import org.fdroid.fdroid.localrepo.SwapService; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.HashMap; import javax.jmdns.JmDNS; @@ -31,10 +33,15 @@ public class BonjourBroadcast extends SwapType { @Override public void start() { - Utils.debugLog(TAG, "Preparing to start Bonjour service."); sendBroadcast(SwapService.EXTRA_STARTING); + InetAddress address = getDeviceAddress(); + if (address == null) { + Log.e(TAG, "Starting Bonjour service, but couldn't ascertain IP address. Seems we are not connected to a network."); + return; + } + /* * a ServiceInfo can only be registered with a single instance * of JmDNS, and there is only ever a single LocalHTTPD port to @@ -43,6 +50,7 @@ public class BonjourBroadcast extends SwapType { if (pairService != null || jmdns != null) { clearCurrentMDNSService(); } + String repoName = Preferences.get().getLocalRepoName(); HashMap values = new HashMap<>(); values.put("path", "/fdroid/repo"); @@ -59,7 +67,7 @@ public class BonjourBroadcast extends SwapType { try { Utils.debugLog(TAG, "Starting bonjour service..."); pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values); - jmdns = JmDNS.create(InetAddress.getByName(FDroidApp.ipAddressString)); + jmdns = JmDNS.create(address); jmdns.registerService(pairService); setConnected(true); Utils.debugLog(TAG, "... Bounjour service started."); @@ -78,12 +86,9 @@ public class BonjourBroadcast extends SwapType { private void clearCurrentMDNSService() { if (jmdns != null) { - if (pairService != null) { - jmdns.unregisterService(pairService); - pairService = null; - } jmdns.unregisterAllServices(); Utils.closeQuietly(jmdns); + pairService = null; jmdns = null; } } @@ -93,4 +98,15 @@ public class BonjourBroadcast extends SwapType { return SwapService.BONJOUR_STATE_CHANGE; } + @Nullable + private InetAddress getDeviceAddress() { + if (FDroidApp.ipAddressString != null) { + try { + return InetAddress.getByName(FDroidApp.ipAddressString); + } catch (UnknownHostException e) { } + } + + return null; + } + } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java index 304be4cbb..1a2ee1a78 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java @@ -99,7 +99,7 @@ public abstract class SwapType { public void run() { SwapType.this.stop(); } - }.run(); + }.start(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiSwap.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiSwap.java index d66e5ed33..8229f692f 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiSwap.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiSwap.java @@ -19,6 +19,13 @@ import java.io.IOException; import java.net.BindException; import java.util.Random; +import rx.Single; +import rx.SingleSubscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + public class WifiSwap extends SwapType { private static final String TAG = "WifiSwap"; @@ -42,55 +49,115 @@ public class WifiSwap extends SwapType { @Override public void start() { - Utils.debugLog(TAG, "Preparing swap webserver."); sendBroadcast(SwapService.EXTRA_STARTING); - Runnable webServer = new Runnable() { - // Tell Eclipse this is not a leak because of Looper use. - @SuppressLint("HandlerLeak") - @Override - public void run() { - localHttpd = new LocalHTTPD( - context, - FDroidApp.ipAddressString, - FDroidApp.port, - context.getFilesDir(), - Preferences.get().isLocalRepoHttpsEnabled()); + if (FDroidApp.ipAddressString == null) { + Log.e(TAG, "Not starting swap webserver, because we don't seem to be connected to a network."); + setConnected(false); + } - Looper.prepare(); // must be run before creating a Handler - webServerThreadHandler = new Handler() { + Single.zip( + Single.create(getWebServerTask()), + Single.create(getBonjourTask()), + new Func2() { @Override - public void handleMessage(Message msg) { - Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj); - setConnected(false); - localHttpd.stop(); + public Boolean call(Boolean webServerTask, Boolean bonjourServiceTask) { + return bonjourServiceTask && webServerTask; } - }; - try { - Utils.debugLog(TAG, "Starting swap webserver..."); - localHttpd.start(); - setConnected(true); - Utils.debugLog(TAG, "Swap webserver started."); - } catch (BindException e) { - int prev = FDroidApp.port; - FDroidApp.port = FDroidApp.port + new Random().nextInt(1111); - setConnected(false); - Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!"); - context.startService(new Intent(context, WifiStateChangeService.class)); - } catch (IOException e) { - setConnected(false); - Log.e(TAG, "Could not start local repo HTTP server", e); - } - Looper.loop(); // start the message receiving loop + }) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.newThread()) + .subscribe(new Action1() { + @Override + public void call(Boolean success) { + setConnected(success); + } + }, + new Action1() { + @Override + public void call(Throwable throwable) { + setConnected(false); + } + }); + } + + /** + * A task which starts the {@link WifiSwap#bonjourBroadcast} and then emits a `true` value at + * the end. + */ + private Single.OnSubscribe getBonjourTask() { + return new Single.OnSubscribe() { + @Override + public void call(SingleSubscriber singleSubscriber) { + bonjourBroadcast.start(); + + // TODO: Be more intelligent about failures here so that we can invoke + // singleSubscriber.onError() in the appropriate circumstances. + singleSubscriber.onSuccess(true); + } + }; + } + + /** + * Constructs a new {@link Thread} for the webserver to run on. If successful, it will also + * populate the webServerThreadHandler property and bind it to that particular thread. This + * allows messages to be sent to the webserver thread by posting messages to that handler. + */ + private Single.OnSubscribe getWebServerTask() { + return new Single.OnSubscribe() { + @Override + public void call(final SingleSubscriber singleSubscriber) { + new Thread(new Runnable() { + // Tell Eclipse this is not a leak because of Looper use. + @SuppressLint("HandlerLeak") + @Override + public void run() { + localHttpd = new LocalHTTPD( + context, + FDroidApp.ipAddressString, + FDroidApp.port, + context.getFilesDir(), + Preferences.get().isLocalRepoHttpsEnabled()); + + Looper.prepare(); // must be run before creating a Handler + webServerThreadHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj); + localHttpd.stop(); + Looper looper = Looper.myLooper(); + if (looper == null) { + Log.e(TAG, "Looper.myLooper() was null for sum reason while shutting down the swap webserver."); + } else { + looper.quit(); + } + } + }; + try { + Utils.debugLog(TAG, "Starting swap webserver..."); + localHttpd.start(); + Utils.debugLog(TAG, "Swap webserver started."); + singleSubscriber.onSuccess(true); + } catch (BindException e) { + int prev = FDroidApp.port; + FDroidApp.port = FDroidApp.port + new Random().nextInt(1111); + context.startService(new Intent(context, WifiStateChangeService.class)); + singleSubscriber.onError(new Exception("port " + prev + " occupied, trying on " + FDroidApp.port + "!")); + } catch (IOException e) { + Log.e(TAG, "Could not start local repo HTTP server", e); + singleSubscriber.onError(e); + } + Looper.loop(); // start the message receiving loop + } + }).start(); } }; - new Thread(webServer).start(); - bonjourBroadcast.start(); } @Override public void stop() { + sendBroadcast(SwapService.EXTRA_STOPPING); if (webServerThreadHandler == null) { Log.i(TAG, "null handler in stopWebServer"); } else { @@ -99,7 +166,13 @@ public class WifiSwap extends SwapType { msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop"; webServerThreadHandler.sendMessage(msg); } + + // Stop the Bonjour stuff after asking the webserver to stop. This is not required in this + // order, but it helps. In practice, the Bonjour stuff takes a second or two to stop. This + // should give enough time for the message we posted above to reach the web server thread + // and for the webserver to thus be stopped. bonjourBroadcast.stop(); + setConnected(false); } } diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java index 0e18b6af7..ccf162460 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -181,7 +181,7 @@ public class WifiStateChangeService extends Service { getApplicationContext().bindService(swapService, new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - ((SwapService.Binder) service).getService().restartWifiIfEnabled(); + ((SwapService.Binder) service).getService().stopWifiIfEnabled(true); getApplicationContext().unbindService(this); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java index bbc17b184..2923865cf 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -302,9 +302,8 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In Utils.debugLog(TAG, "Received onCheckChanged(true) for WiFi swap, asking in background thread to ensure WiFi swap is running."); getManager().getWifiSwap().ensureRunningInBackground(); } else { - Utils.debugLog(TAG, "Received onCheckChanged(false) for WiFi swap, disabling WiFi swap."); - getManager().getWifiSwap().stop(); - Utils.debugLog(TAG, "Received onCheckChanged(false) for WiFi swap, WiFi swap disabled successfully."); + Utils.debugLog(TAG, "Received onCheckChanged(false) for WiFi swap, disabling WiFi swap in background thread."); + getManager().getWifiSwap().stopInBackground(); } uiUpdateWifiNetwork(); } @@ -323,10 +322,15 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In @Override public void onReceive(Context context, Intent intent) { if (intent.hasExtra(SwapService.EXTRA_STARTING)) { - Utils.debugLog(TAG, "WiFi service is starting (setting toggle to visible, but disabled)."); + Utils.debugLog(TAG, "WiFi service is starting (setting toggle to checked, but disabled)."); textWifiVisible.setText(R.string.swap_setting_up_wifi); wifiSwitch.setEnabled(false); wifiSwitch.setChecked(true); + } else if (intent.hasExtra(SwapService.EXTRA_STOPPING)) { + Utils.debugLog(TAG, "WiFi service is stopping (setting toggle to unchecked and disabled)."); + textWifiVisible.setText(R.string.swap_stopping_wifi); + wifiSwitch.setEnabled(false); + wifiSwitch.setChecked(false); } else { wifiSwitch.setEnabled(true); if (intent.hasExtra(SwapService.EXTRA_STARTED)) {