From 7c9492e6b421e49faff56147974006476b6c3d53 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sun, 31 May 2015 22:54:57 +1000 Subject: [PATCH] WIP: Refactoring swap service. Removed LocalRepoService, replaced with SwapService. Still TODO: Manage threads. Currently everything is called from the UI thread, which is a regression from the previous behaviour. I'd like to manage this so that the code interacting with the SwapManager doesn't need to bother itself with whether it is calling from the UI thread or not. The local repo service had many different methods and properties for dealing with starting and stopping various things (webserver, bonjour, in the future it will also need to know about bluetooth and Wifi AP). The SwapService handles this stuff by delegating to specific classes that are only responsible for one of these. Hopefully this will make the process of enabling and disabling swap repos easier to reason about. The local repo service was also stopped and started quite regularly. This meant it was up to the code making use of the service to know if it was running or not, and to enable it if required. The new SwapService is only started once (when the singleton SwapManager is created for the first time). It should not use any more resources, because it is a background service most the time, and it is responsible for moving itself to the foreground when required (the burden is not on the code consuming this service to know when to do this). By having the service running more often, it doesn't need to' continually figure out if it needs to register or unregister listeners for various properties (e.g. https enabled) or wifi broadcasts. The listeners can stay active, and do nothing once notified if swapping is not enabled. Moved the timeout timer (which cancels the swap service after 15 mins) into the SwapService, rather than being managed by the SwapWorkflowActivity. Seems more appropriate for the service to know to time itself out rather than the Activity, seeing as the Activity can die and get GC'ed while the service is still running. Finally, although there is nothing stopping code in F-Droid from talking to the service directly, it is now handled by the SwapManager singleton. This means that details such as using a Messenger or Handler object in order to communicate via arg1 and arg2 is no longer required, and instead methods with proper type signatures can be used. This is similar (but not exactly the same) to how Android system services work. That is, ask for a "Manager" object using getSystemService(), and then use that to perform functionality and query state via that object, which delegates to the service. Then we get the best of both worlds: * Reasonable and type safe method signatures * Services that are not tied to activity lifecycles, which persist beyond the closing of the swap activity. --- F-Droid/AndroidManifest.xml | 2 +- F-Droid/src/org/fdroid/fdroid/FDroidApp.java | 8 - .../fdroid/localrepo/LocalRepoManager.java | 6 + .../fdroid/localrepo/LocalRepoService.java | 312 ------------------ .../{SwapState.java => SwapManager.java} | 100 +++--- .../fdroid/fdroid/localrepo/SwapService.java | 187 +++++++++++ .../fdroid/localrepo/type/BonjourType.java | 87 +++++ .../fdroid/fdroid/localrepo/type/NfcType.java | 27 ++ .../fdroid/localrepo/type/SwapType.java | 11 + .../fdroid/localrepo/type/WebServerType.java | 96 ++++++ .../fdroid/localrepo/type/WifiType.java | 28 ++ .../fdroid/net/WifiStateChangeService.java | 4 +- .../views/swap/ConnectSwapActivity.java | 4 +- .../fdroid/views/swap/JoinWifiView.java | 7 +- .../org/fdroid/fdroid/views/swap/NfcView.java | 7 +- .../fdroid/views/swap/SelectAppsView.java | 8 +- .../fdroid/views/swap/StartSwapView.java | 8 +- .../views/swap/SwapWorkflowActivity.java | 63 ++-- .../fdroid/fdroid/views/swap/WifiQrView.java | 7 +- 19 files changed, 537 insertions(+), 435 deletions(-) delete mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java rename F-Droid/src/org/fdroid/fdroid/localrepo/{SwapState.java => SwapManager.java} (67%) create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml index 9bcc88614..539870340 100644 --- a/F-Droid/AndroidManifest.xml +++ b/F-Droid/AndroidManifest.xml @@ -462,7 +462,7 @@ - + diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java index 6cb5a63ec..07082408c 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java @@ -23,10 +23,8 @@ import android.app.Activity; import android.app.Application; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -34,10 +32,6 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.net.Uri; import android.os.Build; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; import android.preference.PreferenceManager; import android.widget.Toast; @@ -52,14 +46,12 @@ import org.fdroid.fdroid.compat.PRNGFixes; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.InstalledAppCacheUpdater; import org.fdroid.fdroid.data.Repo; -import org.fdroid.fdroid.localrepo.LocalRepoService; import org.fdroid.fdroid.net.IconDownloader; import org.fdroid.fdroid.net.WifiStateChangeService; import java.io.File; import java.security.Security; import java.util.Locale; -import java.util.Set; public class FDroidApp extends Application { diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 3624aef9e..e18f58e66 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -55,6 +55,12 @@ import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; +/** + * The {@link SwapManager} deals with managing the entire workflow from selecting apps to + * swap, to invoking this class to prepare the webroot, to enabling various communication protocols. + * This class deals specifically with the webroot side of things, ensuring we have a valid index.jar + * and the relevant .apk and icon files available. + */ public class LocalRepoManager { private static final String TAG = "LocalRepoManager"; diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java deleted file mode 100644 index 223acf42a..000000000 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java +++ /dev/null @@ -1,312 +0,0 @@ -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.os.AsyncTask; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; -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.Utils; -import org.fdroid.fdroid.net.LocalHTTPD; -import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; - -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"; - - public static final String STATE = "org.fdroid.fdroid.action.LOCAL_REPO_STATE"; - public static final String STARTED = "org.fdroid.fdroid.category.LOCAL_REPO_STARTED"; - 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 final int NOTIFICATION = R.string.local_repo_running; - - private Handler webServerThreadHandler = null; - private LocalHTTPD localHttpd; - private JmDNS jmdns; - private ServiceInfo pairService; - - public static final int START = 1111111; - public static final int STOP = 12345678; - public static final int RESTART = 87654; - - final Messenger messenger = new Messenger(new StartStopHandler(this)); - - /** - * This is most likely going to be created on the UI thread, hence all of - * the message handling will take place on a new thread to prevent blocking - * the UI. - */ - static class StartStopHandler extends Handler { - - private final LocalRepoService service; - - public StartStopHandler(LocalRepoService service) { - this.service = service; - } - - @Override - public void handleMessage(final Message msg) { - new Thread() { - public void run() { - switch (msg.arg1) { - case START: - service.startNetworkServices(); - break; - case STOP: - service.stopNetworkServices(); - break; - case RESTART: - service.stopNetworkServices(); - service.startNetworkServices(); - break; - default: - Log.e(TAG, "Unsupported msg.arg1 (" + msg.arg1 + "), ignored"); - break; - } - } - }.start(); - } - } - - private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent i) { - stopNetworkServices(); - startNetworkServices(); - } - }; - - private ChangeListener localRepoBonjourChangeListener = new ChangeListener() { - @Override - public void onPreferenceChange() { - if (localHttpd.isAlive()) - if (Preferences.get().isLocalRepoBonjourEnabled()) - registerMDNSService(); - else - unregisterMDNSService(); - } - }; - - private final ChangeListener localRepoHttpsChangeListener = new ChangeListener() { - @Override - public void onPreferenceChange() { - Log.i(TAG, "onPreferenceChange"); - if (localHttpd.isAlive()) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - stopNetworkServices(); - startNetworkServices(); - return null; - } - }.execute(); - } - } - }; - - private void showNotification() { - notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - // launch LocalRepoActivity if the user selects this notification - Intent intent = new Intent(this, SwapWorkflowActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - 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)) - .setSmallIcon(R.drawable.ic_swap) - .setContentIntent(contentIntent) - .build(); - startForeground(NOTIFICATION, notification); - } - - @Override - public void onCreate() { - showNotification(); - startNetworkServices(); - Preferences.get().registerLocalRepoBonjourListeners(localRepoBonjourChangeListener); - - LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, - new IntentFilter(WifiStateChangeService.BROADCAST)); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // We want this service to continue running until it is explicitly - // stopped, so return sticky. - return START_STICKY; - } - - @Override - public void onDestroy() { - new Thread() { - public void run() { - stopNetworkServices(); - } - }.start(); - - notificationManager.cancel(NOTIFICATION); - LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange); - Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener); - } - - @Override - public IBinder onBind(Intent intent) { - return messenger.getBinder(); - } - - private void startNetworkServices() { - Log.d(TAG, "Starting local repo network services"); - startWebServer(); - if (Preferences.get().isLocalRepoBonjourEnabled()) - registerMDNSService(); - Preferences.get().registerLocalRepoHttpsListeners(localRepoHttpsChangeListener); - } - - private void stopNetworkServices() { - Log.d(TAG, "Stopping local repo network services"); - Preferences.get().unregisterLocalRepoHttpsListeners(localRepoHttpsChangeListener); - - Log.d(TAG, "Unregistering MDNS service..."); - unregisterMDNSService(); - - Log.d(TAG, "Stopping web server..."); - stopWebServer(); - } - - private void startWebServer() { - Runnable webServer = new Runnable() { - // Tell Eclipse this is not a leak because of Looper use. - @SuppressLint("HandlerLeak") - @Override - public void run() { - localHttpd = new LocalHTTPD( - LocalRepoService.this, - 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(); - } - }; - try { - localHttpd.start(); - } catch (BindException e) { - int prev = FDroidApp.port; - FDroidApp.port = FDroidApp.port + new Random().nextInt(1111); - Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!"); - startService(new Intent(LocalRepoService.this, WifiStateChangeService.class)); - } catch (IOException e) { - Log.e(TAG, "Could not start local repo HTTP server: " + e); - Log.e(TAG, Log.getStackTraceString(e)); - } - Looper.loop(); // start the message receiving loop - } - }; - new Thread(webServer).start(); - Intent intent = new Intent(STATE); - intent.putExtra(STATE, STARTED); - LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent); - } - - private void stopWebServer() { - if (webServerThreadHandler == null) { - Log.i(TAG, "null handler in stopWebServer"); - return; - } - Message msg = webServerThreadHandler.obtainMessage(); - msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop"; - webServerThreadHandler.sendMessage(msg); - Intent intent = new Intent(STATE); - intent.putExtra(STATE, STOPPED); - LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent); - } - - private void registerMDNSService() { - new Thread(new Runnable() { - @Override - public void run() { - /* - * a ServiceInfo can only be registered with a single instance - * of JmDNS, and there is only ever a single LocalHTTPD port to - * advertise anyway. - */ - if (pairService != null || jmdns != null) - clearCurrentMDNSService(); - String repoName = Preferences.get().getLocalRepoName(); - HashMap values = new HashMap<>(); - values.put("path", "/fdroid/repo"); - values.put("name", repoName); - values.put("fingerprint", FDroidApp.repo.fingerprint); - String type; - if (Preferences.get().isLocalRepoHttpsEnabled()) { - values.put("type", "fdroidrepos"); - type = "_https._tcp.local."; - } else { - values.put("type", "fdroidrepo"); - type = "_http._tcp.local."; - } - try { - pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values); - jmdns = JmDNS.create(); - jmdns.registerService(pairService); - } catch (IOException e) { - Log.e(TAG, "Error while registering jmdns service: " + e); - Log.e(TAG, Log.getStackTraceString(e)); - } - } - }).start(); - } - - private void unregisterMDNSService() { - if (localRepoBonjourChangeListener != null) { - Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener); - localRepoBonjourChangeListener = null; - } - clearCurrentMDNSService(); - } - - private void clearCurrentMDNSService() { - if (jmdns != null) { - if (pairService != null) { - jmdns.unregisterService(pairService); - pairService = null; - } - jmdns.unregisterAllServices(); - Utils.closeQuietly(jmdns); - jmdns = null; - } - } -} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java similarity index 67% rename from F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java rename to F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java index bea440d76..9f5e1d1d1 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java @@ -6,11 +6,10 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; import android.support.annotation.IntDef; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -18,21 +17,20 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -public class SwapState { +public class SwapManager { + private static final String TAG = "SwapState"; private static final String SHARED_PREFERENCES = "swap-state"; - - private static final String KEY_STEP = "step"; private static final String KEY_APPS_TO_SWAP = "appsToSwap"; - private static SwapState instance; + private static SwapManager instance; @NonNull - public static SwapState load(@NonNull Context context) { + public static SwapManager load(@NonNull Context context) { if (instance == null) { SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); Set appsToSwap = deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, "")); - instance = new SwapState(context, appsToSwap); + instance = new SwapManager(context, appsToSwap); } return instance; @@ -44,9 +42,11 @@ public class SwapState { @NonNull private Set appsToSwap; - private SwapState(@NonNull Context context, @NonNull Set appsToSwap) { + private SwapManager(@NonNull Context context, @NonNull Set appsToSwap) { this.context = context.getApplicationContext(); this.appsToSwap = appsToSwap; + + setupService(); } /** @@ -81,7 +81,7 @@ public class SwapState { return step; } - public SwapState setStep(@SwapStep int step) { + public SwapManager setStep(@SwapStep int step) { this.step = step; return this; } @@ -114,7 +114,7 @@ public class SwapState { * which is only available in API >= 11. * Package names are reverse-DNS-style, so they should only have alpha numeric values. Thus, * this uses a comma as the separator. - * @see SwapState#deserializePackages(String) + * @see SwapManager#deserializePackages(String) */ private static String serializePackages(Set packages) { StringBuilder sb = new StringBuilder(); @@ -128,7 +128,7 @@ public class SwapState { } /** - * @see SwapState#deserializePackages(String) + * @see SwapManager#deserializePackages(String) */ private static Set deserializePackages(String packages) { Set set = new HashSet<>(); @@ -164,53 +164,61 @@ public class SwapState { // Local repo stop/start/restart handling // ========================================== - private Messenger localRepoServiceMessenger = null; - private boolean localRepoServiceIsBound = false; + @Nullable + private SwapService service = null; - private final ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - localRepoServiceMessenger = new Messenger(service); + private void setupService() { + + ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder binder) { + Log.d(TAG, "Swap service connected, enabling SwapManager to communicate with SwapService."); + service = ((SwapService.Binder)binder).getService(); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "Swap service disconnected"); + service = null; + } + }; + + // The server should not be doing anything or occupying any (noticable) resources + // until we actually ask it to enable swapping. Therefore, we will start it nice and + // early so we don't have to wait until it is connected later. + Intent service = new Intent(context, SwapService.class); + if (context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE)) { + context.startService(service); } - @Override - public void onServiceDisconnected(ComponentName className) { - localRepoServiceMessenger = null; - } - }; + } - public void startLocalRepoService() { - if (!localRepoServiceIsBound) { - Intent service = new Intent(context, LocalRepoService.class); - localRepoServiceIsBound = context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE); - if (localRepoServiceIsBound) - context.startService(service); + public void enableSwapping() { + if (service != null) { + service.enableSwapping(); + } else { + Log.e(TAG, "Couldn't enable swap, because service was not running."); } } - public void stopLocalRepoService() { - if (localRepoServiceIsBound) { - context.unbindService(serviceConnection); - localRepoServiceIsBound = false; + public void disableSwapping() { + if (service != null) { + service.disableSwapping(); + } else { + Log.e(TAG, "Couldn't disable swap, because service was not running."); } - context.stopService(new Intent(context, LocalRepoService.class)); } /** - * Handles checking if the {@link LocalRepoService} is running, and only restarts it if it was running. + * Handles checking if the {@link SwapService} is running, and only restarts it if it was running. */ - public void restartLocalRepoServiceIfRunning() { - if (localRepoServiceMessenger != null) { - try { - Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0); - localRepoServiceMessenger.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } + public void restartIfEnabled() { + if (service != null) { + service.restartIfEnabled(); } } - public boolean isLocalRepoServiceRunning() { - return localRepoServiceIsBound; + public boolean isEnabled() { + return service != null && service.isEnabled(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java new file mode 100644 index 000000000..d4cfbbec5 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java @@ -0,0 +1,187 @@ +package org.fdroid.fdroid.localrepo; + +import android.app.Notification; +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.os.IBinder; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.localrepo.type.BonjourType; +import org.fdroid.fdroid.localrepo.type.NfcType; +import org.fdroid.fdroid.localrepo.type.WebServerType; +import org.fdroid.fdroid.net.WifiStateChangeService; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; + +import java.util.Timer; +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. Currently manages WiFi and NFC. Will manage Bluetooth in + * the future. + * + * TODO: Manage threading correctly. + */ +public class SwapService extends Service { + + private static final String TAG = "SwapService"; + + private static final int NOTIFICATION = 1; + + private final Binder binder = new Binder(); + private final BonjourType bonjourType; + private final WebServerType webServerType; + + // TODO: The NFC type can't really be managed by the service, because it is intrinsically tied + // to a specific _Activity_, and will only be active while that activity is shown. This service + // knows nothing about activities. + private final NfcType nfcType; + + private final static int TIMEOUT = 900000; // 15 mins + + /** + * Used to automatically turn of swapping after a defined amount of time (15 mins). + */ + @Nullable + private Timer timer; + + public class Binder extends android.os.Binder { + public SwapService getService() { + return SwapService.this; + } + } + + public SwapService() { + nfcType = new NfcType(this); + bonjourType = new BonjourType(this); + webServerType = new WebServerType(this); + } + + public void onCreate() { + super.onCreate(); + + Preferences.get().unregisterLocalRepoBonjourListeners(bonjourEnabledListener); + Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener); + + LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, + new IntentFilter(WifiStateChangeService.BROADCAST)); + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + disableSwapping(); + } + + private Notification createNotification() { + Intent intent = new Intent(this, SwapWorkflowActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + return new NotificationCompat.Builder(this) + .setContentTitle(getText(R.string.local_repo_running)) + .setContentText(getText(R.string.touch_to_configure_local_repo)) + .setSmallIcon(R.drawable.ic_swap) + .setContentIntent(contentIntent) + .build(); + } + + private boolean enabled = false; + + public void enableSwapping() { + if (!enabled) { + nfcType.start(); + webServerType.start(); + bonjourType.start(); + startForeground(NOTIFICATION, createNotification()); + enabled = true; + } + + // Regardless of whether it was previously enabled, start the timer again. This ensures that + // if, e.g. a person views the swap activity again, it will attempt to enable swapping if + // appropriate, and thus restart this timer. + initTimer(); + } + + public void disableSwapping() { + if (enabled) { + bonjourType.stop(); + webServerType.stop(); + nfcType.stop(); + stopForeground(true); + if (timer != null) { + timer.cancel(); + } + enabled = false; + } + } + + public boolean isEnabled() { + return enabled; + } + + public void restartIfEnabled() { + if (enabled) { + disableSwapping(); + enableSwapping(); + } + } + + private void initTimer() { + if (timer != null) + timer.cancel(); + + // automatically turn off after 15 minutes + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + disableSwapping(); + } + }, TIMEOUT); + } + + @SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local... + private final Preferences.ChangeListener bonjourEnabledListener = new Preferences.ChangeListener() { + @Override + public void onPreferenceChange() { + Log.i(TAG, "Use Bonjour while swapping preference changed."); + if (enabled) + if (Preferences.get().isLocalRepoBonjourEnabled()) + bonjourType.start(); + else + bonjourType.stop(); + } + }; + + @SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local... + private final Preferences.ChangeListener httpsEnabledListener = new Preferences.ChangeListener() { + @Override + public void onPreferenceChange() { + Log.i(TAG, "Swap over HTTPS preference changed."); + restartIfEnabled(); + } + }; + + @SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local... + private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + restartIfEnabled(); + } + }; + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java new file mode 100644 index 000000000..cfa89803d --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java @@ -0,0 +1,87 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.content.Context; +import android.util.Log; + +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.Utils; + +import java.io.IOException; +import java.util.HashMap; + +import javax.jmdns.JmDNS; +import javax.jmdns.ServiceInfo; + +public class BonjourType implements SwapType { + + private static final String TAG = "BonjourType"; + + private JmDNS jmdns; + private ServiceInfo pairService; + private final Context context; + + public BonjourType(Context context) { + this.context = context; + } + + @Override + public void start() { + + if (Preferences.get().isLocalRepoBonjourEnabled()) + return; + + /* + * a ServiceInfo can only be registered with a single instance + * of JmDNS, and there is only ever a single LocalHTTPD port to + * advertise anyway. + */ + if (pairService != null || jmdns != null) + clearCurrentMDNSService(); + String repoName = Preferences.get().getLocalRepoName(); + HashMap values = new HashMap<>(); + values.put("path", "/fdroid/repo"); + values.put("name", repoName); + values.put("fingerprint", FDroidApp.repo.fingerprint); + String type; + if (Preferences.get().isLocalRepoHttpsEnabled()) { + values.put("type", "fdroidrepos"); + type = "_https._tcp.local."; + } else { + values.put("type", "fdroidrepo"); + type = "_http._tcp.local."; + } + try { + pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values); + jmdns = JmDNS.create(); + jmdns.registerService(pairService); + } catch (IOException e) { + Log.e(TAG, "Error while registering jmdns service: " + e); + Log.e(TAG, Log.getStackTraceString(e)); + } + } + + @Override + public void stop() { + Log.d(TAG, "Unregistering MDNS service..."); + clearCurrentMDNSService(); + } + + private void clearCurrentMDNSService() { + if (jmdns != null) { + if (pairService != null) { + jmdns.unregisterService(pairService); + pairService = null; + } + jmdns.unregisterAllServices(); + Utils.closeQuietly(jmdns); + jmdns = null; + } + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java new file mode 100644 index 000000000..f71130300 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java @@ -0,0 +1,27 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.content.Context; + +public class NfcType implements SwapType { + + private final Context context; + + public NfcType(Context context) { + this.context = context; + } + + @Override + public void start() { + } + + @Override + public void stop() { + + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java new file mode 100644 index 000000000..68161616a --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java @@ -0,0 +1,11 @@ +package org.fdroid.fdroid.localrepo.type; + +public interface SwapType { + + void start(); + + void stop(); + + void restart(); + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java new file mode 100644 index 000000000..18e501577 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java @@ -0,0 +1,96 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.net.LocalHTTPD; +import org.fdroid.fdroid.net.WifiStateChangeService; + +import java.io.IOException; +import java.net.BindException; +import java.util.Random; + +public class WebServerType implements SwapType { + + private static final String TAG = "WebServerType"; + + private Handler webServerThreadHandler = null; + private LocalHTTPD localHttpd; + private final Context context; + + public WebServerType(Context context) { + this.context = context; + } + + @Override + public void start() { + + 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, + 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(); + } + }; + try { + localHttpd.start(); + } catch (BindException e) { + int prev = FDroidApp.port; + FDroidApp.port = FDroidApp.port + new Random().nextInt(1111); + Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!"); + context.startService(new Intent(context, WifiStateChangeService.class)); + } catch (IOException e) { + Log.e(TAG, "Could not start local repo HTTP server: " + e); + Log.e(TAG, Log.getStackTraceString(e)); + } + Looper.loop(); // start the message receiving loop + } + }; + new Thread(webServer).start(); + + // TODO: Don't think these were ever being received by anyone... + /*Intent intent = new Intent(STATE); + intent.putExtra(STATE, STARTED); + LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);*/ + } + + @Override + public void stop() { + if (webServerThreadHandler == null) { + Log.i(TAG, "null handler in stopWebServer"); + return; + } + Message msg = webServerThreadHandler.obtainMessage(); + msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop"; + webServerThreadHandler.sendMessage(msg); + + // TODO: Don't think these were ever being received by anyone... + /*Intent intent = new Intent(STATE); + intent.putExtra(STATE, STOPPED); + LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);*/ + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java new file mode 100644 index 000000000..2ba29851c --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java @@ -0,0 +1,28 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.content.Context; + +public class WifiType implements SwapType { + + private final Context context; + + public WifiType(Context context) { + this.context = context; + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java index 9c2330a04..fa55b8dbe 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -17,7 +17,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; import org.fdroid.fdroid.localrepo.LocalRepoManager; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import java.net.Inet6Address; import java.net.InetAddress; @@ -152,7 +152,7 @@ public class WifiStateChangeService extends Service { Intent intent = new Intent(BROADCAST); LocalBroadcastManager.getInstance(WifiStateChangeService.this).sendBroadcast(intent); WifiStateChangeService.this.stopSelf(); - SwapState.load(WifiStateChangeService.this).restartLocalRepoServiceIfRunning(); + SwapManager.load(WifiStateChangeService.this).restartIfEnabled(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java index 6bb1f7efd..aa682b99a 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java @@ -27,7 +27,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -155,7 +155,7 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi // Only ask server to swap with us, if we are actually running a local repo service. // It is possible to have a swap initiated without first starting a swap, in which // case swapping back is pointless. - if (!newRepoConfig.preventFurtherSwaps() && SwapState.load(this).isLocalRepoServiceRunning()) { + if (!newRepoConfig.preventFurtherSwaps() && SwapManager.load(this).isEnabled()) { askServerToSwapWithUs(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java index 99baf6706..8bf9bd6dc 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java @@ -22,9 +22,8 @@ import android.widget.TextView; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity.InnerView { @@ -122,11 +121,11 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity @Override public int getStep() { - return SwapState.STEP_JOIN_WIFI; + return SwapManager.STEP_JOIN_WIFI; } @Override public int getPreviousStep() { - return SwapState.STEP_SELECT_APPS; + return SwapManager.STEP_SELECT_APPS; } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java index 142218aca..5dba1c7c1 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java @@ -15,8 +15,7 @@ import android.widget.RelativeLayout; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapState; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; +import org.fdroid.fdroid.localrepo.SwapManager; public class NfcView extends RelativeLayout implements SwapWorkflowActivity.InnerView { @@ -70,11 +69,11 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne @Override public int getStep() { - return SwapState.STEP_SHOW_NFC; + return SwapManager.STEP_SHOW_NFC; } @Override public int getPreviousStep() { - return SwapState.STEP_JOIN_WIFI; + return SwapManager.STEP_JOIN_WIFI; } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java index 27ac9a012..dfbf5091e 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java @@ -34,7 +34,7 @@ import android.widget.TextView; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.InstalledAppProvider; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; public class SelectAppsView extends ListView implements SwapWorkflowActivity.InnerView, @@ -62,7 +62,7 @@ public class SelectAppsView extends ListView implements return (SwapWorkflowActivity)getContext(); } - private SwapState getState() { + private SwapManager getState() { return getActivity().getState(); } @@ -136,12 +136,12 @@ public class SelectAppsView extends ListView implements @Override public int getStep() { - return SwapState.STEP_SELECT_APPS; + return SwapManager.STEP_SELECT_APPS; } @Override public int getPreviousStep() { - return SwapState.STEP_INTRO; + return SwapManager.STEP_INTRO; } private void toggleAppSelected(int position) { 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 3f71317bf..f46b21eb3 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -5,15 +5,13 @@ import android.content.Context; import android.os.Build; import android.support.annotation.NonNull; import android.util.AttributeSet; -import android.view.ContextThemeWrapper; import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.widget.LinearLayout; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapState; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; +import org.fdroid.fdroid.localrepo.SwapManager; public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.InnerView { @@ -66,7 +64,7 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. @Override public int getStep() { - return SwapState.STEP_INTRO; + return SwapManager.STEP_INTRO; } @Override @@ -75,6 +73,6 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. // if getStep is STEP_INTRO, don't even bother asking for getPreviousStep. But that is a // bit messy. It would be nicer if this was handled using the same mechanism as everything // else. - return SwapState.STEP_INTRO; + return SwapManager.STEP_INTRO; } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 5a185c672..f6d52d9ec 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -10,7 +10,6 @@ import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -29,37 +28,40 @@ import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.localrepo.LocalRepoManager; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; public class SwapWorkflowActivity extends FragmentActivity { private ViewGroup container; + /** + * A UI component (subclass of {@link View}) which forms part of the swap workflow. + * There is a one to one mapping between an {@link org.fdroid.fdroid.views.swap.SwapWorkflowActivity.InnerView} + * and a {@link org.fdroid.fdroid.localrepo.SwapManager.SwapStep}, and these views know what + * the previous view before them should be. + */ public interface InnerView { /** @return True if the menu should be shown. */ boolean buildMenu(Menu menu, @NonNull MenuInflater inflater); /** @return The step that this view represents. */ - @SwapState.SwapStep int getStep(); + @SwapManager.SwapStep int getStep(); - @SwapState.SwapStep int getPreviousStep(); + @SwapManager.SwapStep int getPreviousStep(); } private static final int CONNECT_TO_SWAP = 1; - private SwapState state; + private SwapManager state; private InnerView currentView; private boolean hasPreparedLocalRepo = false; private UpdateAsyncTask updateSwappableAppsTask = null; - private Timer shutdownLocalRepoTimer; @Override public void onBackPressed() { - if (currentView.getStep() == SwapState.STEP_INTRO) { + if (currentView.getStep() == SwapManager.STEP_INTRO) { finish(); } else { int nextStep = currentView.getPreviousStep(); @@ -71,7 +73,7 @@ public class SwapWorkflowActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - state = SwapState.load(this); + state = SwapManager.load(this); setContentView(R.layout.swap_activity); container = (ViewGroup) findViewById(R.id.fragment_container); showRelevantView(); @@ -98,25 +100,25 @@ public class SwapWorkflowActivity extends FragmentActivity { } switch(state.getStep()) { - case SwapState.STEP_INTRO: + case SwapManager.STEP_INTRO: showIntro(); break; - case SwapState.STEP_SELECT_APPS: + case SwapManager.STEP_SELECT_APPS: showSelectApps(); break; - case SwapState.STEP_SHOW_NFC: + case SwapManager.STEP_SHOW_NFC: showNfc(); break; - case SwapState.STEP_JOIN_WIFI: + case SwapManager.STEP_JOIN_WIFI: showJoinWifi(); break; - case SwapState.STEP_WIFI_QR: + case SwapManager.STEP_WIFI_QR: showWifiQr(); break; } } - public SwapState getState() { + public SwapManager getState() { return state; } @@ -197,36 +199,11 @@ public class SwapWorkflowActivity extends FragmentActivity { } private void ensureLocalRepoRunning() { - if (!getState().isLocalRepoServiceRunning()) { - getState().startLocalRepoService(); - initLocalRepoTimer(900000); // 15 mins - } - } - - private void initLocalRepoTimer(long timeoutMilliseconds) { - - // reset the timer if viewing this Activity again - if (shutdownLocalRepoTimer != null) - shutdownLocalRepoTimer.cancel(); - - // automatically turn off after 15 minutes - shutdownLocalRepoTimer = new Timer(); - shutdownLocalRepoTimer.schedule(new TimerTask() { - @Override - public void run() { - getState().stopLocalRepoService(); - } - }, timeoutMilliseconds); - + getState().enableSwapping(); } public void stopSwapping() { - if (getState().isLocalRepoServiceRunning()) { - if (shutdownLocalRepoTimer != null) { - shutdownLocalRepoTimer.cancel(); - } - getState().stopLocalRepoService(); - } + getState().disableSwapping(); finish(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java index 8b215d505..9a0d4f449 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java @@ -30,9 +30,8 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; import java.net.URI; import java.util.List; @@ -114,13 +113,13 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner @Override public int getStep() { - return SwapState.STEP_WIFI_QR; + return SwapManager.STEP_WIFI_QR; } @Override public int getPreviousStep() { // TODO: Find a way to make this optionally go back to the NFC screen if appropriate. - return SwapState.STEP_JOIN_WIFI; + return SwapManager.STEP_JOIN_WIFI; } private void setUIFromWifi() {