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() {