diff --git a/F-Droid/res/values/strings.xml b/F-Droid/res/values/strings.xml
index 2ce145ddd..60c1322e0 100644
--- a/F-Droid/res/values/strings.xml
+++ b/F-Droid/res/values/strings.xml
@@ -301,6 +301,7 @@
Learn more about Wifi
Swap apps
Swap apps
+ Swap
No network yet
(Tap to open available networks)
It\'s not working
@@ -309,4 +310,5 @@
Do you want to get apps from %1$s now?
Your mobile device becomes an app store with Swap!
START A SWAP
+ An error occurred while attempting to swap with another device. We will still try to get apps from them, but they may not be able to get apps from us.
diff --git a/F-Droid/src/org/fdroid/fdroid/FDroid.java b/F-Droid/src/org/fdroid/fdroid/FDroid.java
index 1b29bdd51..607ce98c9 100644
--- a/F-Droid/src/org/fdroid/fdroid/FDroid.java
+++ b/F-Droid/src/org/fdroid/fdroid/FDroid.java
@@ -198,7 +198,9 @@ public class FDroid extends ActionBarActivity {
if (parser.isValidRepo()) {
intent.putExtra("handled", true);
if (parser.isFromSwap()) {
- startActivityForResult(new Intent(ACTION_ADD_REPO, intent.getData(), this, ConnectSwapActivity.class), REQUEST_SWAP);
+ Intent confirmIntent = new Intent(this, ConnectSwapActivity.class);
+ confirmIntent.setData(intent.getData());
+ startActivityForResult(confirmIntent, REQUEST_SWAP);
} else {
startActivity(new Intent(ACTION_ADD_REPO, intent.getData(), this, ManageReposActivity.class));
}
diff --git a/F-Droid/src/org/fdroid/fdroid/Utils.java b/F-Droid/src/org/fdroid/fdroid/Utils.java
index 924568bf0..c7e7c7bd2 100644
--- a/F-Droid/src/org/fdroid/fdroid/Utils.java
+++ b/F-Droid/src/org/fdroid/fdroid/Utils.java
@@ -285,14 +285,25 @@ public final class Utils {
}
@NonNull
+ public static Uri getLocalRepoUri(Repo repo) {
+ if (TextUtils.isEmpty(repo.address))
+ return Uri.parse("http://wifi-not-enabled");
+ Uri uri = Uri.parse(repo.address);
+ Uri.Builder b = uri.buildUpon();
+ if (!TextUtils.isEmpty(repo.fingerprint))
+ b.appendQueryParameter("fingerprint", repo.fingerprint);
+ String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https" : "http";
+ b.scheme(scheme);
+ return b.build();
+ }
+
public static Uri getSharingUri(Repo repo) {
if (TextUtils.isEmpty(repo.address))
return Uri.parse("http://wifi-not-enabled");
- Uri uri = Uri.parse(repo.address.replaceFirst("http", "fdroidrepo"));
- Uri.Builder b = uri.buildUpon();
+ Uri localRepoUri = getLocalRepoUri(repo);
+ Uri.Builder b = localRepoUri.buildUpon();
+ b.scheme(localRepoUri.getScheme().replaceFirst("http", "fdroidrepo"));
b.appendQueryParameter("swap", "1");
- if (!TextUtils.isEmpty(repo.fingerprint))
- b.appendQueryParameter("fingerprint", repo.fingerprint);
if (!TextUtils.isEmpty(FDroidApp.bssid)) {
b.appendQueryParameter("bssid", Uri.encode(FDroidApp.bssid));
if (!TextUtils.isEmpty(FDroidApp.ssid))
diff --git a/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java b/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java
index 6e41123ed..e55d8c94f 100644
--- a/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java
+++ b/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java
@@ -7,6 +7,7 @@ import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.views.swap.ConnectSwapActivity;
import java.util.Arrays;
import java.util.Locale;
@@ -27,6 +28,7 @@ public class NewRepoConfig {
private String bssid;
private String ssid;
private boolean fromSwap;
+ private boolean preventFurtherSwaps;
public NewRepoConfig(Context context, String uri) {
init(context, uri != null ? Uri.parse(uri) : null);
@@ -34,6 +36,7 @@ public class NewRepoConfig {
public NewRepoConfig(Context context, Intent intent) {
init(context, intent.getData());
+ preventFurtherSwaps = intent.getBooleanExtra(ConnectSwapActivity.EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, false);
}
private void init(Context context, Uri incomingUri) {
@@ -110,14 +113,28 @@ public class NewRepoConfig {
return port;
}
- public String getUriString() {
+ public String getRepoUriString() {
return uriString;
}
- public Uri getUri() {
+ /**
+ * This is the URI which was passed to the NewRepoConfig for parsing.
+ * Not that it may be an fdroidrepo:// or http:// scheme, and it may also have
+ * ssid, bssid, and perhaps other query parameters. If you want the actual repo
+ * URL, then you will probably want {@link org.fdroid.fdroid.data.NewRepoConfig#getRepoUri()}.
+ */
+ public Uri getParsedUri() {
return uri;
}
+ public Uri getRepoUri() {
+ if (uriString == null) {
+ return null;
+ } else {
+ return Uri.parse(uriString);
+ }
+ }
+
public String getHost() {
return host;
}
@@ -138,6 +155,10 @@ public class NewRepoConfig {
return fromSwap;
}
+ public boolean preventFurtherSwaps() {
+ return preventFurtherSwaps;
+ }
+
/*
* The port starts out as 8888, but if there is a conflict, it will be
* incremented until there is a free port found.
diff --git a/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java b/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java
index 193f3625e..f3fed2679 100644
--- a/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java
+++ b/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java
@@ -1,11 +1,14 @@
package org.fdroid.fdroid.net;
import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
import android.util.Log;
import android.webkit.MimeTypeMap;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
+import org.fdroid.fdroid.views.swap.ConnectSwapActivity;
import java.io.File;
import java.io.FileInputStream;
@@ -19,6 +22,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.HashMap;
import java.util.StringTokenizer;
import javax.net.ssl.SSLServerSocketFactory;
@@ -68,8 +72,54 @@ public class LocalHTTPD extends NanoHTTPD {
return newUri;
}
+ private void requestSwap(String repo) {
+ Log.d(TAG, "Received request to swap with " + repo);
+ Log.d(TAG, "Showing confirm screen to check whether that is okay with the user.");
+
+ Uri repoUri = Uri.parse(repo);
+ Intent intent = new Intent(context, ConnectSwapActivity.class);
+ intent.setData(repoUri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(ConnectSwapActivity.EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, true);
+ context.startActivity(intent);
+ }
+
@Override
public Response serve(IHTTPSession session) {
+
+ if (session.getMethod() == Method.POST) {
+ try {
+ session.parseBody(new HashMap());
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage());
+ return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "Internal server error, check logcat on server for details.");
+ } catch (ResponseException re) {
+ return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
+ }
+
+ return handlePost(session);
+ } else {
+ return handleGet(session);
+ }
+ }
+
+ private Response handlePost(IHTTPSession session) {
+ Uri uri = Uri.parse(session.getUri());
+ switch(uri.getPath()) {
+ case "/request-swap":
+ if (!session.getParms().containsKey("repo")) {
+ Log.e(TAG, "Malformed /request-swap request to local repo HTTP server. Should have posted a 'repo' parameter." );
+ return new Response(Response.Status.BAD_REQUEST, MIME_PLAINTEXT, "Requires 'repo' parameter to be posted.");
+ } else {
+ requestSwap(session.getParms().get("repo"));
+ return new Response(Response.Status.OK, MIME_PLAINTEXT, "Swap request received.");
+ }
+ }
+ return new Response("");
+ }
+
+ private Response handleGet(IHTTPSession session) {
+
Map header = session.getHeaders();
Map parms = session.getParms();
String uri = session.getUri();
diff --git a/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java b/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java
index fb6ad1148..097a5bfc9 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java
@@ -584,7 +584,7 @@ public class ManageReposActivity extends ActionBarActivity {
/* an URL from a click, NFC, QRCode scan, etc */
NewRepoConfig newRepoConfig = new NewRepoConfig(this, intent);
if (newRepoConfig.isValidRepo()) {
- importRepo(newRepoConfig.getUriString(), newRepoConfig.getFingerprint());
+ importRepo(newRepoConfig.getRepoUriString(), newRepoConfig.getFingerprint());
checkIfNewRepoOnSameWifi(newRepoConfig);
} else if (newRepoConfig.getErrorMessage() != null) {
Toast.makeText(this, newRepoConfig.getErrorMessage(), Toast.LENGTH_LONG).show();
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java
index e5da26df8..353cff0f2 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java
@@ -3,6 +3,8 @@ package org.fdroid.fdroid.views.swap;
import android.app.Activity;
import android.content.ContentValues;
import android.net.Uri;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -13,13 +15,22 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import org.fdroid.fdroid.ProgressListener;
-import org.fdroid.fdroid.R;
-import org.fdroid.fdroid.UpdateService;
+import android.widget.Toast;
+import org.apache.http.HttpHost;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.message.BasicNameValuePair;
+import org.fdroid.fdroid.*;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
public class ConfirmReceiveSwapFragment extends Fragment implements ProgressListener {
private static final String TAG = "fdroid.ConfirmReceiveSwapFragment";
@@ -31,23 +42,19 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-
View view = inflater.inflate(R.layout.swap_confirm_receive, container, false);
-
view.findViewById(R.id.no_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
-
view.findViewById(R.id.yes_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
confirm();
}
});
-
return view;
}
@@ -65,26 +72,34 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
);
} else {
// TODO: Show error message on screen (not in popup).
+ // TODO: I don't think we want to continue with this at all if the repo config is invalid,
+ // how should we boot the user from this screen in this case?
}
}
private void confirm() {
- this.repo = ensureRepoExists();
- UpdateService.updateRepoNow(this.repo.address, getActivity()).setListener(this);
+ repo = ensureRepoExists();
+ if (repo != null) {
+ UpdateService.updateRepoNow(repo.address, getActivity()).setListener(this);
+ }
}
@NonNull
private Repo ensureRepoExists() {
- // TODO: newRepoConfig.getUri() will include a fingerprint, which may not match with
- // the repos address in the database.
- Repo repo = RepoProvider.Helper.findByAddress(getActivity(), newRepoConfig.getUriString());
+ if (!newRepoConfig.isValidRepo()) {
+ return null;
+ }
+
+ // 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(getActivity(), newRepoConfig.getRepoUriString());
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, "Swap");
- values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getUriString());
+ values.put(RepoProvider.DataColumns.NAME, getString(R.string.swap_repo_name));
+ values.put(RepoProvider.DataColumns.ADDRESS, newRepoConfig.getRepoUriString());
values.put(RepoProvider.DataColumns.DESCRIPTION, ""); // TODO;
values.put(RepoProvider.DataColumns.FINGERPRINT, newRepoConfig.getFingerprint());
values.put(RepoProvider.DataColumns.IN_USE, true);
@@ -97,9 +112,64 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
values.put(RepoProvider.DataColumns.IS_SWAP, true);
RepoProvider.Helper.update(getActivity(), repo, values);
}
+
+ if (!newRepoConfig.preventFurtherSwaps()) {
+ askServerToSwapWithUs();
+ }
+
return repo;
}
+ 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", getActivity());
+ 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() {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(
+ getActivity(),
+ getString(R.string.swap_reciprocate_failed),
+ Toast.LENGTH_LONG
+ ).show();
+ }
+ });
+ }
+ }.execute();
+ }
+
@Override
public void onProgress(Event event) {
// TODO: Show progress, but we can worry about that later.
@@ -123,4 +193,8 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList
break;
}
}
+
+ public void setRepoConfig(NewRepoConfig repoConfig) {
+ this.newRepoConfig = repoConfig;
+ }
}
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 946cd3e77..642408856 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java
@@ -4,6 +4,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
+import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.data.Repo;
@@ -11,6 +12,17 @@ public class ConnectSwapActivity extends FragmentActivity {
private static final String STATE_CONFIRM = "startSwap";
+ /**
+ * When connecting to a swap, we then go and initiate a connection with that
+ * device and ask if it would like to swap with us. Upon receiving that request
+ * and agreeing, we don't then want to be asked whether we want to swap back.
+ * This flag protects against two devices continually going back and forth
+ * among each other offering swaps.
+ */
+ public static final String EXTRA_PREVENT_FURTHER_SWAP_REQUESTS = "preventFurtherSwap";
+
+ private ConfirmReceiveSwapFragment fragment;
+
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -18,9 +30,11 @@ public class ConnectSwapActivity extends FragmentActivity {
if (savedInstanceState == null) {
+ fragment = new ConfirmReceiveSwapFragment();
+
getSupportFragmentManager()
.beginTransaction()
- .replace(android.R.id.content, new ConfirmReceiveSwapFragment(), STATE_CONFIRM)
+ .replace(android.R.id.content, fragment, STATE_CONFIRM)
.addToBackStack(STATE_CONFIRM)
.commit();
@@ -28,6 +42,14 @@ public class ConnectSwapActivity extends FragmentActivity {
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Only confirm the action, and then return a result...
+ NewRepoConfig config = new NewRepoConfig(this, getIntent());
+ fragment.setRepoConfig(config);
+ }
+
@Override
public void onBackPressed() {
if (currentState().equals(STATE_CONFIRM)) {