diff --git a/F-Droid/res/layout/swap_activity.xml b/F-Droid/res/layout/swap_activity.xml
index 8349507a5..9551080ab 100644
--- a/F-Droid/res/layout/swap_activity.xml
+++ b/F-Droid/res/layout/swap_activity.xml
@@ -6,16 +6,16 @@
android:orientation="vertical">
+ android:id="@+id/toolbar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:titleTextAppearance="@style/SwapTheme.Wizard.Text"
+ android:minHeight="?attr/actionBarSize"
+ android:background="?attr/colorPrimary" />
+ android:id="@+id/fragment_container"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
\ No newline at end of file
diff --git a/F-Droid/res/layout/swap_initial_loading.xml b/F-Droid/res/layout/swap_initial_loading.xml
new file mode 100644
index 000000000..d1e169875
--- /dev/null
+++ b/F-Droid/res/layout/swap_initial_loading.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/F-Droid/res/values/strings.xml b/F-Droid/res/values/strings.xml
index 8966e549c..0d279f21f 100644
--- a/F-Droid/res/values/strings.xml
+++ b/F-Droid/res/values/strings.xml
@@ -347,4 +347,5 @@
May work
Promising
Best bet
+ Loading...
diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java
index e18f58e66..c43e61bb7 100644
--- a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java
+++ b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java
@@ -56,7 +56,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
/**
- * The {@link SwapManager} deals with managing the entire workflow from selecting apps to
+ * The {@link SwapService} 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.
diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java
deleted file mode 100644
index d89f8003e..000000000
--- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java
+++ /dev/null
@@ -1,461 +0,0 @@
-package org.fdroid.fdroid.localrepo;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.IBinder;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.fdroid.fdroid.FDroidApp;
-import org.fdroid.fdroid.UpdateService;
-import org.fdroid.fdroid.data.App;
-import org.fdroid.fdroid.data.Repo;
-import org.fdroid.fdroid.data.RepoProvider;
-import org.fdroid.fdroid.localrepo.peers.Peer;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class SwapManager {
-
- private static final String TAG = "SwapState";
- private static final String SHARED_PREFERENCES = "swap-state";
- private static final String KEY_APPS_TO_SWAP = "appsToSwap";
-
- private static SwapManager instance;
-
- @NonNull
- 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 SwapManager(context, appsToSwap);
- }
-
- return instance;
- }
-
- @NonNull
- private final Context context;
-
- @NonNull
- private Set appsToSwap;
-
- @NonNull
- private List peers;
-
- private SwapManager(@NonNull Context context, @NonNull Set appsToSwap) {
- this.context = context.getApplicationContext();
- this.appsToSwap = appsToSwap;
- this.peers = new ArrayList<>();
-
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Peer peer = (Peer) intent.getSerializableExtra(EXTRA_PEER);
- if (!peers.contains(peer)) {
- peers.add(peer);
- }
- }
- }, new IntentFilter(ACTION_PEER_FOUND));
-
- setupService();
- }
-
- /**
- * Where relevant, the state of the swap process will be saved to disk using preferences.
- * Note that this is not always useful, for example saving the "current wifi network" is
- * bound to cause trouble when the user opens the swap process again and is connected to
- * a different network.
- */
- private SharedPreferences persistence() {
- return context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_APPEND);
- }
-
- // ==========================================================
- // Search for peers to swap
- // ==========================================================
-
- public void scanForPeers() {
- if (service != null) {
- Log.d(TAG, "Scanning for nearby devices to swap with...");
- service.scanForPeers();
- } else {
- Log.e(TAG, "Couldn't scan for peers, because service was not running.");
- }
- }
-
- public void cancelScanningForPeers() {
- if (service != null) {
- service.cancelScanningForPeers();
- } else {
- Log.e(TAG, "Couldn't cancel scanning for peers, because service was not running.");
- }
- }
-
- @NonNull
- public List getPeers() {
- return peers;
- }
-
-
- // ==========================================================
- // Manage the current step
- // ("Step" refers to the current view being shown in the UI)
- // ==========================================================
-
- public static final int STEP_INTRO = 1;
- public static final int STEP_SELECT_APPS = 2;
- public static final int STEP_JOIN_WIFI = 3;
- public static final int STEP_SHOW_NFC = 4;
- public static final int STEP_WIFI_QR = 5;
- public static final int STEP_CONNECTING = 6;
- public static final int STEP_SUCCESS = 7;
- public static final int STEP_CONFIRM_SWAP = 8;
-
- private @SwapStep int step = STEP_INTRO;
-
- /**
- * Current screen that the swap process is up to.
- * Will be one of the SwapState.STEP_* values.
- */
- @SwapStep
- public int getStep() {
- return step;
- }
-
- public SwapManager setStep(@SwapStep int step) {
- this.step = step;
- return this;
- }
-
- public @NonNull Set getAppsToSwap() {
- return appsToSwap;
- }
-
- @Nullable
- public UpdateService.UpdateReceiver refreshSwap() {
- return this.peer != null ? connectTo(peer) : null;
- }
-
- @NonNull
- public UpdateService.UpdateReceiver connectTo(@NonNull Peer peer) {
- if (peer != this.peer) {
- Log.e(TAG, "Oops, got a different peer to swap with than initially planned.");
- }
-
- peerRepo = ensureRepoExists(peer);
-
- // 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() && isEnabled()) {
- askServerToSwapWithUs();
- }*/
-
- return UpdateService.updateRepoNow(peer.getRepoAddress(), context, false);
- }
-/*
- private void askServerToSwapWithUs() {
- if (!newRepoConfig.isValidRepo()) {
- return;
- }
-
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... args) {
- Uri repoUri = newRepoConfig.getRepoUri();
- String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
-
- AndroidHttpClient client = AndroidHttpClient.newInstance("F-Droid", ConnectSwapActivity.this);
- HttpPost request = new HttpPost("/request-swap");
- HttpHost host = new HttpHost(repoUri.getHost(), repoUri.getPort(), repoUri.getScheme());
-
- try {
- Log.d(TAG, "Asking server at " + newRepoConfig.getRepoUriString() + " to swap with us in return (by POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\")...");
- populatePostParams(swapBackUri, request);
- client.execute(host, request);
- } catch (IOException e) {
- notifyOfErrorOnUiThread();
- Log.e(TAG, "Error while asking server to swap with us: " + e.getMessage());
- } finally {
- client.close();
- }
- return null;
- }
-
- private void populatePostParams(String swapBackUri, HttpPost request) throws UnsupportedEncodingException {
- List params = new ArrayList<>();
- params.add(new BasicNameValuePair("repo", swapBackUri));
- UrlEncodedFormEntity encodedParams = new UrlEncodedFormEntity(params);
- request.setEntity(encodedParams);
- }
-
- private void notifyOfErrorOnUiThread() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(
- ConnectSwapActivity.this,
- R.string.swap_reciprocate_failed,
- Toast.LENGTH_LONG
- ).show();
- }
- });
- }
- }.execute();
- }*/
- private Repo ensureRepoExists(@NonNull Peer peer) {
- // TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with
- // the repos address in the database. Not sure on best behaviour in this situation.
- Repo repo = RepoProvider.Helper.findByAddress(context, peer.getRepoAddress());
- if (repo == null) {
- ContentValues values = new ContentValues(6);
-
- // TODO: i18n and think about most appropriate name. Although it wont be visible in
- // the "Manage repos" UI after being marked as a swap repo here...
- values.put(RepoProvider.DataColumns.NAME, peer.getName());
- values.put(RepoProvider.DataColumns.ADDRESS, peer.getRepoAddress());
- values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
- values.put(RepoProvider.DataColumns.FINGERPRINT, peer.getFingerprint());
- values.put(RepoProvider.DataColumns.IN_USE, true);
- values.put(RepoProvider.DataColumns.IS_SWAP, true);
- Uri uri = RepoProvider.Helper.insert(context, values);
- repo = RepoProvider.Helper.findByUri(context, uri);
- }
-
- return repo;
- }
-
- @Nullable
- public Repo getPeerRepo() {
- return peerRepo;
- }
-
- public void install(@NonNull final App app) {
-
- }
-
- /**
- * Ensure that we don't get put into an incorrect state, by forcing people to pass valid
- * states to setStep. Ideally this would be done by requiring an enum or something to
- * be passed rather than in integer, however that is harder to persist on disk than an int.
- * This is the same as, e.g. {@link Context#getSystemService(String)}
- */
- @IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR,
- STEP_CONNECTING, STEP_SUCCESS, STEP_CONFIRM_SWAP})
- @Retention(RetentionPolicy.SOURCE)
- public @interface SwapStep {}
-
-
- // =================================================
- // Have selected a specific peer to swap with
- // (Rather than showing a generic QR code to scan)
- // =================================================
-
- @Nullable
- private Peer peer;
-
- @Nullable
- private Repo peerRepo;
-
- public void swapWith(Peer peer) {
- this.peer = peer;
- }
-
- public boolean isConnectingWithPeer() {
- return peer != null;
- }
-
- @Nullable
- public Peer getPeer() {
- return peer;
- }
-
-
- // ==========================================
- // Remember apps user wants to swap
- // ==========================================
-
- private void persistAppsToSwap() {
- persistence().edit().putString(KEY_APPS_TO_SWAP, serializePackages(appsToSwap)).commit();
- }
-
- /**
- * Replacement for {@link android.content.SharedPreferences.Editor#putStringSet(String, Set)}
- * 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 SwapManager#deserializePackages(String)
- */
- private static String serializePackages(Set packages) {
- StringBuilder sb = new StringBuilder();
- for (String pkg : packages) {
- if (sb.length() > 0) {
- sb.append(',');
- }
- sb.append(pkg);
- }
- return sb.toString();
- }
-
- /**
- * @see SwapManager#deserializePackages(String)
- */
- private static Set deserializePackages(String packages) {
- Set set = new HashSet<>();
- if (!TextUtils.isEmpty(packages)) {
- Collections.addAll(set, packages.split(","));
- }
- return set;
- }
-
- public void ensureFDroidSelected() {
- String fdroid = context.getPackageName();
- if (!hasSelectedPackage(fdroid)) {
- selectPackage(fdroid);
- }
- }
-
- public boolean hasSelectedPackage(String packageName) {
- return appsToSwap.contains(packageName);
- }
-
- public void selectPackage(String packageName) {
- appsToSwap.add(packageName);
- persistAppsToSwap();
- }
-
- public void deselectPackage(String packageName) {
- if (appsToSwap.contains(packageName)) {
- appsToSwap.remove(packageName);
- }
- persistAppsToSwap();
- }
-
-
- // ==========================================
- // Local repo stop/start/restart handling
- // ==========================================
-
- @Nullable
- private SwapService service = null;
-
- 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);
- }
-
- }
-
- public void enableSwapping() {
- if (service != null) {
- service.enableSwapping();
- } else {
- Log.e(TAG, "Couldn't enable swap, because service was not running.");
- }
- }
-
- public void disableSwapping() {
- if (service != null) {
- service.disableSwapping();
- } else {
- Log.e(TAG, "Couldn't disable swap, because service was not running.");
- }
- setStep(STEP_INTRO);
- }
-
- /**
- * Handles checking if the {@link SwapService} is running, and only restarts it if it was running.
- */
- public void restartIfEnabled() {
- if (service != null) {
- service.restartIfEnabled();
- }
- }
-
- public boolean isEnabled() {
- return service != null && service.isEnabled();
- }
-
- // ==========================================
- // Interacting with Bluetooth adapter
- // ==========================================
-
- public boolean isBluetoothDiscoverable() {
- return service != null && service.getBluetooth().isConnected();
- }
-
- public void ensureBluetoothDiscoverable() {
- if (service != null) {
- service.getBluetooth().start();
- }
- }
-
- public void makeBluetoothNonDiscoverable() {
- if (service != null) {
- service.getBluetooth().stop();
- }
- }
-
- private boolean isWifiConnected() {
- return !TextUtils.isEmpty(FDroidApp.ssid);
- }
-
- public boolean isBonjourDiscoverable() {
- return isWifiConnected() && service != null && service.isEnabled();
- }
-
- public void ensureBonjourDiscoverable() {
- if (!isBonjourDiscoverable()) {
- // TODO: Enable bonjour (currently it is enabled by default when the service starts)
- }
- }
-
- public void makeBonjourNotDiscoverable() {
- if (service != null) {
- // TODO: Disable bonjour (currently it is enabled by default when the service starts)
- }
- }
-
- public boolean isScanningForPeers() {
- return service != null && service.isScanningForPeers();
- }
-
- public static final String ACTION_PEER_FOUND = "org.fdroid.fdroid.SwapManager.ACTION_PEER_FOUND";
- public static final String EXTRA_PEER = "EXTRA_PEER";
-}
diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java
index ab0a761c7..c9e0eed39 100644
--- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java
+++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java
@@ -4,20 +4,32 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.IBinder;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
import android.util.Log;
+import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.UpdateService;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.localrepo.peers.BluetoothFinder;
import org.fdroid.fdroid.localrepo.peers.BonjourFinder;
+import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.localrepo.type.BluetoothType;
import org.fdroid.fdroid.localrepo.type.BonjourType;
import org.fdroid.fdroid.localrepo.type.SwapType;
@@ -25,6 +37,12 @@ import org.fdroid.fdroid.localrepo.type.WebServerType;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
@@ -35,105 +53,290 @@ import java.util.TimerTask;
*/
public class SwapService extends Service {
- private static final String TAG = "SwapService";
+ private static final String TAG = "SwapManager";
+ private static final String SHARED_PREFERENCES = "swap-state";
+ private static final String KEY_APPS_TO_SWAP = "appsToSwap";
- public static final String BONJOUR_STATE_CHANGE = "org.fdroid.fdroid.BONJOUR_STATE_CHANGE";
- public static final String BLUETOOTH_STATE_CHANGE = "org.fdroid.fdroid.BLUETOOTH_STATE_CHANGE";
- public static final String EXTRA_STARTING = "STARTING";
- public static final String EXTRA_STARTED = "STARTED";
- public static final String EXTRA_STOPPED = "STOPPED";
+ @NonNull
+ private Set appsToSwap = new HashSet<>();
- private static final int NOTIFICATION = 1;
-
- private final Binder binder = new Binder();
- private SwapType bonjourType;
- private SwapType bluetoothType;
- private SwapType webServerType;
-
- private BonjourFinder bonjourFinder;
- private BluetoothFinder bluetoothFinder;
-
- private final static int TIMEOUT = 900000; // 15 mins
+ public SwapService() {
+ super();
+ }
/**
- * Used to automatically turn of swapping after a defined amount of time (15 mins).
+ * Where relevant, the state of the swap process will be saved to disk using preferences.
+ * Note that this is not always useful, for example saving the "current wifi network" is
+ * bound to cause trouble when the user opens the swap process again and is connected to
+ * a different network.
*/
- @Nullable
- private Timer timer;
-
- public boolean isScanningForPeers() {
- return bonjourFinder.isScanning() || bluetoothFinder.isScanning();
+ private SharedPreferences persistence() {
+ return getSharedPreferences(SHARED_PREFERENCES, MODE_APPEND);
}
- public SwapType getBluetooth() {
- return bluetoothType;
- }
-
- public SwapType getBonjour() {
- return bluetoothType;
- }
-
- public class Binder extends android.os.Binder {
- public SwapService getService() {
- return SwapService.this;
- }
- }
-
- public void onCreate() {
- super.onCreate();
-
- bonjourType = new BonjourType(this);
- bluetoothType = BluetoothType.create(this);
- webServerType = new WebServerType(this);
- bonjourFinder = new BonjourFinder(this);
- bluetoothFinder = new BluetoothFinder(this);
-
- Log.d(TAG, "Creating service, will register appropriate listeners.");
- Preferences.get().registerLocalRepoBonjourListeners(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();
- Log.d(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
- disableSwapping();
- Preferences.get().unregisterLocalRepoBonjourListeners(bonjourEnabledListener);
- Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
- LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
- }
-
- 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();
- }
+ // ==========================================================
+ // Search for peers to swap
+ // ==========================================================
public void scanForPeers() {
+ Log.d(TAG, "Scanning for nearby devices to swap with...");
bonjourFinder.scan();
bluetoothFinder.scan();
}
- public void cancelScanningForPeers() {
+ public void stopScanningForPeers() {
bonjourFinder.cancel();
bluetoothFinder.cancel();
}
- private boolean enabled = false;
+
+ // ==========================================================
+ // Manage the current step
+ // ("Step" refers to the current view being shown in the UI)
+ // ==========================================================
+
+ public static final int STEP_INTRO = 1;
+ public static final int STEP_SELECT_APPS = 2;
+ public static final int STEP_JOIN_WIFI = 3;
+ public static final int STEP_SHOW_NFC = 4;
+ public static final int STEP_WIFI_QR = 5;
+ public static final int STEP_CONNECTING = 6;
+ public static final int STEP_SUCCESS = 7;
+ public static final int STEP_CONFIRM_SWAP = 8;
+
+ /**
+ * Special view, that we don't really want to actually store against the
+ * {@link SwapService#step}. Rather, we use it for the purpose of specifying
+ * we are in the state waiting for the {@link SwapService} to get started and
+ * bound to the {@link SwapWorkflowActivity}.
+ */
+ public static final int STEP_INITIAL_LOADING = 9;
+
+ private @SwapStep int step = STEP_INTRO;
+
+ /**
+ * Current screen that the swap process is up to.
+ * Will be one of the SwapState.STEP_* values.
+ */
+ @SwapStep
+ public int getStep() {
+ return step;
+ }
+
+ public SwapService setStep(@SwapStep int step) {
+ this.step = step;
+ return this;
+ }
+
+ public @NonNull Set getAppsToSwap() {
+ return appsToSwap;
+ }
+
+ @Nullable
+ public UpdateService.UpdateReceiver refreshSwap() {
+ return this.peer != null ? connectTo(peer) : null;
+ }
+
+ @NonNull
+ public UpdateService.UpdateReceiver connectTo(@NonNull Peer peer) {
+ if (peer != this.peer) {
+ Log.e(TAG, "Oops, got a different peer to swap with than initially planned.");
+ }
+
+ peerRepo = ensureRepoExists(peer);
+
+ // 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() && isEnabled()) {
+ askServerToSwapWithUs();
+ }*/
+
+ return UpdateService.updateRepoNow(peer.getRepoAddress(), this, false);
+ }
+/*
+ private void askServerToSwapWithUs() {
+ if (!newRepoConfig.isValidRepo()) {
+ return;
+ }
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... args) {
+ Uri repoUri = newRepoConfig.getRepoUri();
+ String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
+
+ AndroidHttpClient client = AndroidHttpClient.newInstance("F-Droid", ConnectSwapActivity.this);
+ HttpPost request = new HttpPost("/request-swap");
+ HttpHost host = new HttpHost(repoUri.getHost(), repoUri.getPort(), repoUri.getScheme());
+
+ try {
+ Log.d(TAG, "Asking server at " + newRepoConfig.getRepoUriString() + " to swap with us in return (by POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\")...");
+ populatePostParams(swapBackUri, request);
+ client.execute(host, request);
+ } catch (IOException e) {
+ notifyOfErrorOnUiThread();
+ Log.e(TAG, "Error while asking server to swap with us: " + e.getMessage());
+ } finally {
+ client.close();
+ }
+ return null;
+ }
+
+ private void populatePostParams(String swapBackUri, HttpPost request) throws UnsupportedEncodingException {
+ List params = new ArrayList<>();
+ params.add(new BasicNameValuePair("repo", swapBackUri));
+ UrlEncodedFormEntity encodedParams = new UrlEncodedFormEntity(params);
+ request.setEntity(encodedParams);
+ }
+
+ private void notifyOfErrorOnUiThread() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(
+ ConnectSwapActivity.this,
+ R.string.swap_reciprocate_failed,
+ Toast.LENGTH_LONG
+ ).show();
+ }
+ });
+ }
+ }.execute();
+ }*/
+
+ private Repo ensureRepoExists(@NonNull Peer peer) {
+ // TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with
+ // the repos address in the database. Not sure on best behaviour in this situation.
+ Repo repo = RepoProvider.Helper.findByAddress(this, peer.getRepoAddress());
+ if (repo == null) {
+ ContentValues values = new ContentValues(6);
+
+ // TODO: i18n and think about most appropriate name. Although it wont be visible in
+ // the "Manage repos" UI after being marked as a swap repo here...
+ values.put(RepoProvider.DataColumns.NAME, peer.getName());
+ values.put(RepoProvider.DataColumns.ADDRESS, peer.getRepoAddress());
+ values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
+ values.put(RepoProvider.DataColumns.FINGERPRINT, peer.getFingerprint());
+ values.put(RepoProvider.DataColumns.IN_USE, true);
+ values.put(RepoProvider.DataColumns.IS_SWAP, true);
+ Uri uri = RepoProvider.Helper.insert(this, values);
+ repo = RepoProvider.Helper.findByUri(this, uri);
+ }
+
+ return repo;
+ }
+
+ @Nullable
+ public Repo getPeerRepo() {
+ return peerRepo;
+ }
+
+ public void install(@NonNull final App app) {
+
+ }
+
+ /**
+ * Ensure that we don't get put into an incorrect state, by forcing people to pass valid
+ * states to setStep. Ideally this would be done by requiring an enum or something to
+ * be passed rather than in integer, however that is harder to persist on disk than an int.
+ * This is the same as, e.g. {@link Context#getSystemService(String)}
+ */
+ @IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR,
+ STEP_CONNECTING, STEP_SUCCESS, STEP_CONFIRM_SWAP, STEP_INITIAL_LOADING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SwapStep {}
+
+
+ // =================================================
+ // Have selected a specific peer to swap with
+ // (Rather than showing a generic QR code to scan)
+ // =================================================
+
+ @Nullable
+ private Peer peer;
+
+ @Nullable
+ private Repo peerRepo;
+
+ public void swapWith(Peer peer) {
+ this.peer = peer;
+ }
+
+ public boolean isConnectingWithPeer() {
+ return peer != null;
+ }
+
+ @Nullable
+ public Peer getPeer() {
+ return peer;
+ }
+
+
+ // ==========================================
+ // Remember apps user wants to swap
+ // ==========================================
+
+ private void persistAppsToSwap() {
+ persistence().edit().putString(KEY_APPS_TO_SWAP, serializePackages(appsToSwap)).commit();
+ }
+
+ /**
+ * Replacement for {@link android.content.SharedPreferences.Editor#putStringSet(String, Set)}
+ * 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 SwapService#deserializePackages(String)
+ */
+ private static String serializePackages(Set packages) {
+ StringBuilder sb = new StringBuilder();
+ for (String pkg : packages) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(pkg);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @see SwapService#deserializePackages(String)
+ */
+ private static Set deserializePackages(String packages) {
+ Set set = new HashSet<>();
+ if (!TextUtils.isEmpty(packages)) {
+ Collections.addAll(set, packages.split(","));
+ }
+ return set;
+ }
+
+ public void ensureFDroidSelected() {
+ String fdroid = getPackageName();
+ if (!hasSelectedPackage(fdroid)) {
+ selectPackage(fdroid);
+ }
+ }
+
+ public boolean hasSelectedPackage(String packageName) {
+ return appsToSwap.contains(packageName);
+ }
+
+ public void selectPackage(String packageName) {
+ appsToSwap.add(packageName);
+ persistAppsToSwap();
+ }
+
+ public void deselectPackage(String packageName) {
+ if (appsToSwap.contains(packageName)) {
+ appsToSwap.remove(packageName);
+ }
+ persistAppsToSwap();
+ }
+
+
+ // ==========================================
+ // Local repo stop/start/restart handling
+ // ==========================================
/**
* Ensures that the webserver is running, as are the other services which make swap work.
@@ -165,17 +368,6 @@ public class SwapService extends Service {
initTimer();
}
- /**
- * The guts of this class - responsible for enabling the relevant services for swapping.
- * Doesn't know anything about enabled/disabled state, you should check that before invoking
- * this method so it doesn't start something that is already started.
- * Runs asynchronously on several background threads.
- */
- private void enableSwappingAsynchronous() {
- webServerType.startInBackground();
- bonjourType.startInBackground();
- }
-
public void disableSwapping() {
if (enabled) {
new AsyncTask() {
@@ -206,18 +398,8 @@ public class SwapService extends Service {
}
/**
- * @see SwapService#enableSwappingAsynchronous()
+ * Handles checking if the {@link SwapService} is running, and only restarts it if it was running.
*/
- private void disableSwappingSynchronous() {
- Log.d(TAG, "Disabling SwapService (bonjour, webserver, etc)");
- bonjourType.stop();
- webServerType.stop();
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
public void restartIfEnabled() {
if (enabled) {
new AsyncTask() {
@@ -232,6 +414,169 @@ public class SwapService extends Service {
}
}
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ // ==========================================
+ // Interacting with Bluetooth adapter
+ // ==========================================
+
+ public boolean isBluetoothDiscoverable() {
+ return bluetoothType.isConnected();
+ }
+
+ public void ensureBluetoothDiscoverable() {
+ bluetoothType.start();
+ }
+
+ public void makeBluetoothNonDiscoverable() {
+ bluetoothType.stop();
+ }
+
+ private boolean isWifiConnected() {
+ return !TextUtils.isEmpty(FDroidApp.ssid);
+ }
+
+ public boolean isBonjourDiscoverable() {
+ return isWifiConnected() && isEnabled();
+ }
+
+ public void ensureBonjourDiscoverable() {
+ if (!isBonjourDiscoverable()) {
+ // TODO: Enable bonjour (currently it is enabled by default when the service starts)
+ }
+ }
+
+ public void makeBonjourNotDiscoverable() {
+ // TODO: Disable bonjour (currently it is enabled by default when the service starts)
+ }
+
+ public boolean isScanningForPeers() {
+ return bonjourFinder.isScanning() || bluetoothFinder.isScanning();
+ }
+
+ public static final String ACTION_PEER_FOUND = "org.fdroid.fdroid.SwapManager.ACTION_PEER_FOUND";
+ public static final String EXTRA_PEER = "EXTRA_PEER";
+
+
+ // ===============================================================
+ // Old SwapService stuff being merged into that.
+ // ===============================================================
+
+ public static final String BONJOUR_STATE_CHANGE = "org.fdroid.fdroid.BONJOUR_STATE_CHANGE";
+ public static final String BLUETOOTH_STATE_CHANGE = "org.fdroid.fdroid.BLUETOOTH_STATE_CHANGE";
+ public static final String EXTRA_STARTING = "STARTING";
+ public static final String EXTRA_STARTED = "STARTED";
+ public static final String EXTRA_STOPPED = "STOPPED";
+
+ private static final int NOTIFICATION = 1;
+
+ private final Binder binder = new Binder();
+ private SwapType bonjourType;
+ private SwapType bluetoothType;
+ private SwapType webServerType;
+
+ private BonjourFinder bonjourFinder;
+ private BluetoothFinder bluetoothFinder;
+
+ 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 SwapType getBluetooth() {
+ return bluetoothType;
+ }
+
+ public SwapType getBonjour() {
+ return bluetoothType;
+ }
+
+ public class Binder extends android.os.Binder {
+ public SwapService getService() {
+ return SwapService.this;
+ }
+ }
+
+ public void onCreate() {
+ super.onCreate();
+
+ Log.d(TAG, "Creating swap service.");
+
+ SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
+
+ appsToSwap.addAll(deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, "")));
+ bonjourType = new BonjourType(this);
+ bluetoothType = BluetoothType.create(this);
+ webServerType = new WebServerType(this);
+ bonjourFinder = new BonjourFinder(this);
+ bluetoothFinder = new BluetoothFinder(this);
+
+ Preferences.get().registerLocalRepoBonjourListeners(bonjourEnabledListener);
+ Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener);
+
+ LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, new IntentFilter(WifiStateChangeService.BROADCAST));
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
+ disableSwapping();
+ Preferences.get().unregisterLocalRepoBonjourListeners(bonjourEnabledListener);
+ Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
+ }
+
+ 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;
+
+ /**
+ * The guts of this class - responsible for enabling the relevant services for swapping.
+ * Doesn't know anything about enabled/disabled state, you should check that before invoking
+ * this method so it doesn't start something that is already started.
+ * Runs asynchronously on several background threads.
+ */
+ private void enableSwappingAsynchronous() {
+ webServerType.startInBackground();
+ bonjourType.startInBackground();
+ }
+
+ /**
+ * @see SwapService#enableSwappingAsynchronous()
+ */
+ private void disableSwappingSynchronous() {
+ Log.d(TAG, "Disabling SwapService (bonjour, webserver, etc)");
+ bonjourType.stop();
+ webServerType.stop();
+ }
+
private void initTimer() {
if (timer != null) {
Log.d(TAG, "Cancelling existing timer");
diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/peers/PeerFinder.java b/F-Droid/src/org/fdroid/fdroid/localrepo/peers/PeerFinder.java
index 452bda7c2..aa62f88b1 100644
--- a/F-Droid/src/org/fdroid/fdroid/localrepo/peers/PeerFinder.java
+++ b/F-Droid/src/org/fdroid/fdroid/localrepo/peers/PeerFinder.java
@@ -2,14 +2,13 @@ package org.fdroid.fdroid.localrepo.peers;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.util.Log;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
/**
* Searches for other devices in the vicinity, using specific technologies.
- * Once found, sends an {@link SwapManager#ACTION_PEER_FOUND} intent with the {@link SwapManager#EXTRA_PEER}
+ * Once found, sends an {@link SwapService#ACTION_PEER_FOUND} intent with the {@link SwapService#EXTRA_PEER}
* extra attribute set to the subclass of {@link Peer} that was found.
*/
public abstract class PeerFinder {
@@ -32,8 +31,8 @@ public abstract class PeerFinder {
protected void foundPeer(T peer) {
Log.i(TAG, "Found peer " + peer.getName());
- Intent intent = new Intent(SwapManager.ACTION_PEER_FOUND);
- intent.putExtra(SwapManager.EXTRA_PEER, peer);
+ Intent intent = new Intent(SwapService.ACTION_PEER_FOUND);
+ intent.putExtra(SwapService.EXTRA_PEER, peer);
context.sendBroadcast(intent);
}
diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java
index 18a4b4e71..f6b6ac099 100644
--- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java
+++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java
@@ -3,7 +3,6 @@ package org.fdroid.fdroid.localrepo.type;
import android.content.Context;
import android.util.Log;
-import org.fdroid.fdroid.FDroid;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java
index 3b5171a26..df688998a 100644
--- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java
+++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java
@@ -1,8 +1,10 @@
package org.fdroid.fdroid.net;
import android.app.Service;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -17,7 +19,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.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -159,7 +161,17 @@ public class WifiStateChangeService extends Service {
Intent intent = new Intent(BROADCAST);
LocalBroadcastManager.getInstance(WifiStateChangeService.this).sendBroadcast(intent);
WifiStateChangeService.this.stopSelf();
- SwapManager.load(WifiStateChangeService.this).restartIfEnabled();
+
+ Intent swapService = new Intent(WifiStateChangeService.this, SwapService.class);
+ bindService(swapService, new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ ((SwapService.Binder)service).getService().restartIfEnabled();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ }, BIND_AUTO_CREATE);
}
}
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java b/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java
index 15382b20b..70367326f 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java
@@ -5,18 +5,13 @@ import android.content.Context;
import android.os.Build;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
-import android.support.v4.view.MenuItemCompat;
import android.util.AttributeSet;
import android.view.Menu;
import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
import android.widget.RelativeLayout;
-import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivity.InnerView {
@@ -41,7 +36,7 @@ public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivi
return (SwapWorkflowActivity)getContext();
}
- private SwapManager getManager() {
+ private SwapService getManager() {
return getActivity().getState();
}
@@ -59,12 +54,12 @@ public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivi
@Override
public int getStep() {
- return SwapManager.STEP_CONFIRM_SWAP;
+ return SwapService.STEP_CONFIRM_SWAP;
}
@Override
public int getPreviousStep() {
- return SwapManager.STEP_INTRO;
+ return SwapService.STEP_INTRO;
}
@ColorRes
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 aa682b99a..76d662806 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java
@@ -1,12 +1,15 @@
package org.fdroid.fdroid.views.swap;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.net.Uri;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
@@ -27,7 +30,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.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -152,16 +155,39 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi
repo = RepoProvider.Helper.findByUri(this, uri);
}
- // 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() && SwapManager.load(this).isEnabled()) {
- askServerToSwapWithUs();
- }
+ attemptSwapBack();
return repo;
}
+ /**
+ * 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.
+ */
+ private void attemptSwapBack() {
+
+ if (newRepoConfig.preventFurtherSwaps()) {
+ return;
+ }
+
+ ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ SwapService service = ((SwapService.Binder) binder).getService();
+ if (service.isEnabled()) {
+ askServerToSwapWithUs();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+
+ Intent intent = new Intent(this, SwapService.class);
+ bindService(intent, connection, BIND_AUTO_CREATE);
+ }
+
private void askServerToSwapWithUs() {
if (!newRepoConfig.isValidRepo()) {
return;
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/InitialLoadingView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/InitialLoadingView.java
new file mode 100644
index 000000000..d3be23001
--- /dev/null
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/InitialLoadingView.java
@@ -0,0 +1,68 @@
+package org.fdroid.fdroid.views.swap;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.v4.view.MenuItemCompat;
+import android.util.AttributeSet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.RelativeLayout;
+
+import org.fdroid.fdroid.Preferences;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.localrepo.SwapService;
+
+public class InitialLoadingView extends RelativeLayout implements SwapWorkflowActivity.InnerView {
+
+ public InitialLoadingView(Context context) {
+ super(context);
+ }
+
+ public InitialLoadingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public InitialLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public InitialLoadingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private SwapWorkflowActivity getActivity() {
+ return (SwapWorkflowActivity)getContext();
+ }
+
+ @Override
+ public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) {
+ return true;
+ }
+
+ @Override
+ public int getStep() {
+ return SwapService.STEP_INITIAL_LOADING;
+ }
+
+ @Override
+ public int getPreviousStep() {
+ return SwapService.STEP_JOIN_WIFI;
+ }
+
+ @ColorRes
+ public int getToolbarColour() {
+ return getResources().getColor(R.color.swap_blue);
+ }
+
+ @Override
+ public String getToolbarTitle() {
+ return getResources().getString(R.string.swap);
+ }
+}
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 b606b3ddf..7f352fae2 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java
@@ -23,7 +23,7 @@ import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.net.WifiStateChangeService;
public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity.InnerView {
@@ -122,12 +122,12 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity
@Override
public int getStep() {
- return SwapManager.STEP_JOIN_WIFI;
+ return SwapService.STEP_JOIN_WIFI;
}
@Override
public int getPreviousStep() {
- return SwapManager.STEP_INTRO;
+ return SwapService.STEP_INTRO;
}
@ColorRes
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 ab8da2d32..d5bd3d251 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java
@@ -16,7 +16,7 @@ import android.widget.RelativeLayout;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
public class NfcView extends RelativeLayout implements SwapWorkflowActivity.InnerView {
@@ -70,12 +70,12 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne
@Override
public int getStep() {
- return SwapManager.STEP_SHOW_NFC;
+ return SwapService.STEP_SHOW_NFC;
}
@Override
public int getPreviousStep() {
- return SwapManager.STEP_JOIN_WIFI;
+ return SwapService.STEP_JOIN_WIFI;
}
@ColorRes
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 6651cbf19..b8288830f 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java
@@ -36,7 +36,7 @@ import android.widget.TextView;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.data.InstalledAppProvider;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
public class SelectAppsView extends ListView implements
SwapWorkflowActivity.InnerView,
@@ -64,7 +64,7 @@ public class SelectAppsView extends ListView implements
return (SwapWorkflowActivity)getContext();
}
- private SwapManager getState() {
+ private SwapService getState() {
return getActivity().getState();
}
@@ -119,12 +119,12 @@ public class SelectAppsView extends ListView implements
@Override
public int getStep() {
- return SwapManager.STEP_SELECT_APPS;
+ return SwapService.STEP_SELECT_APPS;
}
@Override
public int getPreviousStep() {
- return getState().isConnectingWithPeer() ? SwapManager.STEP_JOIN_WIFI : SwapManager.STEP_INTRO;
+ return getState().isConnectingWithPeer() ? SwapService.STEP_JOIN_WIFI : SwapService.STEP_INTRO;
}
@ColorRes
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 abd1a32db..5a042378d 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java
@@ -6,8 +6,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.os.Build;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
@@ -23,10 +21,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.ScrollView;
@@ -35,7 +31,6 @@ import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.localrepo.SwapManager;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.net.WifiStateChangeService;
@@ -97,7 +92,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In
return (SwapWorkflowActivity)getContext();
}
- private SwapManager getManager() {
+ private SwapService getManager() {
return getActivity().getState();
}
@@ -175,7 +170,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In
getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- Peer peer = intent.getParcelableExtra(SwapManager.EXTRA_PEER);
+ Peer peer = intent.getParcelableExtra(SwapService.EXTRA_PEER);
if (adapter.getPosition(peer) >= 0) {
Log.d(TAG, "Found peer: " + peer + ", ignoring though, because it is already in our list.");
} else {
@@ -184,7 +179,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In
uiUpdatePeersInfo();
}
}
- }, new IntentFilter(SwapManager.ACTION_PEER_FOUND));
+ }, new IntentFilter(SwapService.ACTION_PEER_FOUND));
}
@@ -241,12 +236,12 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In
if (intent.hasExtra(SwapService.EXTRA_STARTING)) {
Log.d(TAG, "Bluetooth service is starting...");
bluetoothSwitch.setEnabled(false);
- bluetoothSwitch.setChecked(true);
+ // bluetoothSwitch.setChecked(true);
} else {
bluetoothSwitch.setEnabled(true);
if (intent.hasExtra(SwapService.EXTRA_STARTED)) {
Log.d(TAG, "Bluetooth service has started.");
- bluetoothSwitch.setChecked(true);
+ // bluetoothSwitch.setChecked(true);
} else {
Log.d(TAG, "Bluetooth service has stopped.");
bluetoothSwitch.setChecked(false);
@@ -336,7 +331,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In
@Override
public int getStep() {
- return SwapManager.STEP_INTRO;
+ return SwapService.STEP_INTRO;
}
@Override
@@ -345,7 +340,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In
// 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 SwapManager.STEP_INTRO;
+ return SwapService.STEP_INTRO;
}
@Override
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapAppsView.java
index fc7089c8c..cadef72f6 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapAppsView.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapAppsView.java
@@ -20,7 +20,6 @@ import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -39,7 +38,7 @@ import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.Repo;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import java.util.Timer;
import java.util.TimerTask;
@@ -70,7 +69,7 @@ public class SwapAppsView extends ListView implements
return (SwapWorkflowActivity)getContext();
}
- private SwapManager getState() {
+ private SwapService getState() {
return getActivity().getState();
}
@@ -175,12 +174,12 @@ public class SwapAppsView extends ListView implements
@Override
public int getStep() {
- return SwapManager.STEP_SUCCESS;
+ return SwapService.STEP_SUCCESS;
}
@Override
public int getPreviousStep() {
- return SwapManager.STEP_INTRO;
+ return SwapService.STEP_INTRO;
}
@ColorRes
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapConnecting.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapConnecting.java
index a4d6015b3..ba0d34e33 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapConnecting.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapConnecting.java
@@ -5,28 +5,19 @@ import android.content.Context;
import android.os.Build;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
-import android.support.v4.view.MenuItemCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import android.widget.TextView;
-import android.widget.Toast;
-import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.ProgressListener;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
-import org.fdroid.fdroid.localrepo.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.peers.Peer;
-import java.util.List;
-
public class SwapConnecting extends LinearLayout implements SwapWorkflowActivity.InnerView {
private final static String TAG = "SwapConnecting";
@@ -52,7 +43,7 @@ public class SwapConnecting extends LinearLayout implements SwapWorkflowActivity
return (SwapWorkflowActivity)getContext();
}
- private SwapManager getManager() {
+ private SwapService getManager() {
return getActivity().getState();
}
@@ -116,12 +107,12 @@ public class SwapConnecting extends LinearLayout implements SwapWorkflowActivity
@Override
public int getStep() {
- return SwapManager.STEP_CONNECTING;
+ return SwapService.STEP_CONNECTING;
}
@Override
public int getPreviousStep() {
- return SwapManager.STEP_SELECT_APPS;
+ return SwapService.STEP_SELECT_APPS;
}
@ColorRes
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 7d20102a8..0a73d70de 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java
@@ -2,14 +2,18 @@ package org.fdroid.fdroid.views.swap;
import android.app.Activity;
import android.app.ProgressDialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.IBinder;
import android.support.annotation.ColorRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
@@ -31,7 +35,7 @@ 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.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.peers.Peer;
import java.util.Arrays;
@@ -45,7 +49,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
/**
* 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
+ * and a {@link SwapService.SwapStep}, and these views know what
* the previous view before them should be.
*/
public interface InnerView {
@@ -53,9 +57,9 @@ public class SwapWorkflowActivity extends AppCompatActivity {
boolean buildMenu(Menu menu, @NonNull MenuInflater inflater);
/** @return The step that this view represents. */
- @SwapManager.SwapStep int getStep();
+ @SwapService.SwapStep int getStep();
- @SwapManager.SwapStep int getPreviousStep();
+ @SwapService.SwapStep int getPreviousStep();
@ColorRes int getToolbarColour();
@@ -66,18 +70,57 @@ public class SwapWorkflowActivity extends AppCompatActivity {
private static final int CONNECT_TO_SWAP = 1;
private Toolbar toolbar;
- private SwapManager state;
private InnerView currentView;
private boolean hasPreparedLocalRepo = false;
private PrepareSwapRepo updateSwappableAppsTask = null;
+ @Nullable
+ private SwapService service = null;
+
+ @NonNull
+ public SwapService getService() {
+ if (service == null) {
+ // *Slightly* more informative than a null-pointer error that would otherwise happen.
+ throw new IllegalStateException("Trying to access swap service before it was initialized.");
+ }
+ return 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();
+ showRelevantView();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d(TAG, "Swap service disconnected");
+ service = null;
+ // TODO: What to do about the UI in this instance?
+ }
+ };
+
+ // The server should not be doing anything or occupying any (noticeable) 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(this, SwapService.class);
+ if (bindService(service, serviceConnection, Context.BIND_AUTO_CREATE)) {
+ startService(service);
+ }
+
+ }
+
@Override
public void onBackPressed() {
- if (currentView.getStep() == SwapManager.STEP_INTRO) {
+ if (currentView.getStep() == SwapService.STEP_INTRO) {
finish();
} else {
int nextStep = currentView.getPreviousStep();
- state.setStep(nextStep);
+ getService().setStep(nextStep);
showRelevantView();
}
}
@@ -85,7 +128,9 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- state = SwapManager.load(this);
+
+ setupService();
+
setContentView(R.layout.swap_activity);
toolbar = (Toolbar) findViewById(R.id.toolbar);
@@ -93,14 +138,13 @@ public class SwapWorkflowActivity extends AppCompatActivity {
setSupportActionBar(toolbar);
container = (ViewGroup) findViewById(R.id.fragment_container);
- showRelevantView();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
boolean parent = super.onPrepareOptionsMenu(menu);
- boolean inner = currentView.buildMenu(menu, getMenuInflater());
+ boolean inner = currentView != null && currentView.buildMenu(menu, getMenuInflater());
return parent || inner;
}
@@ -111,32 +155,38 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
private void showRelevantView() {
- if (currentView != null && currentView.getStep() == state.getStep()) {
- // Already showing the currect step, so don't bother changing anything.
+
+ if (service == null) {
+ showInitialLoading();
return;
}
- switch(state.getStep()) {
- case SwapManager.STEP_INTRO:
+ if (container.getVisibility() == View.GONE || currentView != null && currentView.getStep() == service.getStep()) {
+ // Already showing the correct step, so don't bother changing anything.
+ return;
+ }
+
+ switch(service.getStep()) {
+ case SwapService.STEP_INTRO:
showIntro();
break;
- case SwapManager.STEP_SELECT_APPS:
+ case SwapService.STEP_SELECT_APPS:
showSelectApps();
break;
- case SwapManager.STEP_SHOW_NFC:
+ case SwapService.STEP_SHOW_NFC:
showNfc();
break;
- case SwapManager.STEP_JOIN_WIFI:
+ case SwapService.STEP_JOIN_WIFI:
showJoinWifi();
break;
- case SwapManager.STEP_WIFI_QR:
+ case SwapService.STEP_WIFI_QR:
showWifiQr();
break;
}
}
- public SwapManager getState() {
- return state;
+ public SwapService getState() {
+ return service;
}
private void showNfc() {
@@ -149,7 +199,16 @@ public class SwapWorkflowActivity extends AppCompatActivity {
container.removeAllViews();
View view = ((LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(viewRes, container, false);
currentView = (InnerView)view;
- state.setStep(currentView.getStep());
+
+ // Don't actually set the step to STEP_INITIAL_LOADING, as we are going to use this view
+ // purely as a placeholder for _whatever view is meant to be shown_.
+ if (currentView.getStep() != SwapService.STEP_INITIAL_LOADING) {
+ if (service == null) {
+ throw new IllegalStateException("We are not in the STEP_INITIAL_LOADING state, but the service is not ready.");
+ }
+ service.setStep(currentView.getStep());
+ }
+
toolbar.setBackgroundColor(currentView.getToolbarColour());
toolbar.setTitle(currentView.getToolbarTitle());
toolbar.setNavigationIcon(R.drawable.ic_close_white);
@@ -164,15 +223,19 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
private void onToolbarCancel() {
- SwapManager.load(this).disableSwapping();
+ getService().disableSwapping();
finish();
}
+ private void showInitialLoading() {
+ inflateInnerView(R.layout.swap_initial_loading);
+ }
+
private void showIntro() {
- if (!state.isEnabled()) {
+ if (!getService().isEnabled()) {
prepareInitialRepo();
}
- SwapManager.load(this).scanForPeers();
+ getService().scanForPeers();
inflateInnerView(R.layout.swap_blank);
}
@@ -190,7 +253,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
// Otherwise, probably will need to scan the file system.
public void onAppsSelected() {
if (updateSwappableAppsTask == null && !hasPreparedLocalRepo) {
- updateSwappableAppsTask = new PrepareFullSwapRepo(state.getAppsToSwap());
+ updateSwappableAppsTask = new PrepareFullSwapRepo(getService().getAppsToSwap());
updateSwappableAppsTask.execute();
} else if (!attemptToShowNfc()) {
showWifiQr();
@@ -218,11 +281,11 @@ public class SwapWorkflowActivity extends AppCompatActivity {
private void onLocalRepoPrepared() {
updateSwappableAppsTask = null;
hasPreparedLocalRepo = true;
- if (state.isConnectingWithPeer()) {
+ if (getService().isConnectingWithPeer()) {
startSwappingWithPeer();
} else if (!attemptToShowNfc()) {
showWifiQr();
- };
+ }
}
private void startSwappingWithPeer() {
@@ -263,7 +326,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
public void swapWith(Peer peer) {
- state.swapWith(peer);
+ getService().swapWith(peer);
showSelectApps();
}
@@ -291,7 +354,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@Override
protected void onPreExecute() {
- state.enableSwapping();
+ getService().enableSwapping();
super.onPreExecute();
}
}
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 8737524f2..c2e8354d0 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java
@@ -31,7 +31,7 @@ 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.SwapManager;
+import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.net.WifiStateChangeService;
import java.net.URI;
@@ -114,13 +114,13 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner
@Override
public int getStep() {
- return SwapManager.STEP_WIFI_QR;
+ return SwapService.STEP_WIFI_QR;
}
@Override
public int getPreviousStep() {
// TODO: Find a way to make this optionally go back to the NFC screen if appropriate.
- return SwapManager.STEP_JOIN_WIFI;
+ return SwapService.STEP_JOIN_WIFI;
}
@ColorRes