diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml index e0124ff81..3ec830c8a 100644 --- a/F-Droid/AndroidManifest.xml +++ b/F-Droid/AndroidManifest.xml @@ -298,18 +298,6 @@ android:name="android.app.default_searchable" android:value=".SearchResults" /> - - - - + diff --git a/F-Droid/src/javax/jmdns/impl/FDroidServiceInfo.java b/F-Droid/src/javax/jmdns/impl/FDroidServiceInfo.java index 657131ce5..c4fba32f3 100644 --- a/F-Droid/src/javax/jmdns/impl/FDroidServiceInfo.java +++ b/F-Droid/src/javax/jmdns/impl/FDroidServiceInfo.java @@ -21,6 +21,14 @@ public class FDroidServiceInfo extends ServiceInfoImpl implements Parcelable { super(info); } + public String getFingerprint() { + return getPropertyString("fingerprint"); + } + + public String getRepoAddress() { + return getURL(); // Automatically appends the "path" property if present, so no need to do it ourselves. + } + private static byte[] readBytes(Parcel in) { byte[] bytes = new byte[in.readInt()]; in.readByteArray(bytes); diff --git a/F-Droid/src/org/fdroid/fdroid/FDroid.java b/F-Droid/src/org/fdroid/fdroid/FDroid.java index 8cbddc22e..f946b1ccf 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroid.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroid.java @@ -49,7 +49,6 @@ import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.views.AppListFragmentPagerAdapter; import org.fdroid.fdroid.views.ManageReposActivity; -import org.fdroid.fdroid.views.swap.ConnectSwapActivity; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class FDroid extends ActionBarActivity { @@ -208,7 +207,8 @@ public class FDroid extends ActionBarActivity { if (parser.isValidRepo()) { intent.putExtra("handled", true); if (parser.isFromSwap()) { - Intent confirmIntent = new Intent(this, ConnectSwapActivity.class); + Intent confirmIntent = new Intent(this, SwapWorkflowActivity.class); + confirmIntent.putExtra(SwapWorkflowActivity.EXTRA_CONFIRM, true); confirmIntent.setData(intent.getData()); startActivityForResult(confirmIntent, REQUEST_SWAP); } else { diff --git a/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java b/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java index 5792fbf1e..500269311 100644 --- a/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java +++ b/F-Droid/src/org/fdroid/fdroid/data/NewRepoConfig.java @@ -7,7 +7,8 @@ import android.text.TextUtils; import android.util.Log; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.views.swap.ConnectSwapActivity; +import org.fdroid.fdroid.localrepo.peers.WifiPeer; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; import java.util.Arrays; import java.util.Locale; @@ -20,10 +21,8 @@ public class NewRepoConfig { private boolean isValidRepo = false; private String uriString; - private Uri uri; private String host; private int port = -1; - private String scheme; private String fingerprint; private String bssid; private String ssid; @@ -36,12 +35,12 @@ public class NewRepoConfig { public NewRepoConfig(Context context, Intent intent) { init(context, intent.getData()); - preventFurtherSwaps = intent.getBooleanExtra(ConnectSwapActivity.EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, false); + preventFurtherSwaps = intent.getBooleanExtra(SwapWorkflowActivity.EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, false); } private void init(Context context, Uri incomingUri) { /* an URL from a click, NFC, QRCode scan, etc */ - uri = incomingUri; + Uri uri = incomingUri; if (uri == null) { isValidRepo = false; return; @@ -50,7 +49,7 @@ public class NewRepoConfig { Log.d(TAG, "Parsing incoming intent looking for repo: " + incomingUri); // scheme and host should only ever be pure ASCII aka Locale.ENGLISH - scheme = uri.getScheme(); + String scheme = uri.getScheme(); host = uri.getHost(); port = uri.getPort(); if (TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)) { @@ -109,14 +108,6 @@ public class NewRepoConfig { public String getRepoUriString() { return uriString; } - /** - * 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; @@ -126,8 +117,6 @@ public class NewRepoConfig { public String getHost() { return host; } - public String getScheme() { return scheme; } - public String getFingerprint() { return fingerprint; } public boolean isValidRepo() { return isValidRepo; } @@ -136,14 +125,6 @@ public class NewRepoConfig { 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. - */ - public boolean looksLikeLocalRepo() { - return (port >= 8888 && host.matches("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")); - } - public String getErrorMessage() { return errorMessage; } /** Sanitize and format an incoming repo URI for function and readability */ @@ -158,4 +139,8 @@ public class NewRepoConfig { .replace("fdroidrepo", "http") // proper repo address .replace("/FDROID/REPO", "/fdroid/repo"); // for QR FDroid path } + + public WifiPeer toPeer() { + return new WifiPeer(this); + } } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java b/F-Droid/src/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java index 3418a4fe3..02bae516a 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/peers/BonjourPeer.java @@ -2,16 +2,10 @@ package org.fdroid.fdroid.localrepo.peers; import android.os.Parcel; -import org.fdroid.fdroid.R; - -import java.net.Inet4Address; -import java.net.Inet6Address; - import javax.jmdns.impl.FDroidServiceInfo; import javax.jmdns.ServiceInfo; -import javax.jmdns.impl.ServiceInfoImpl; -public class BonjourPeer implements Peer { +public class BonjourPeer extends WifiPeer { private FDroidServiceInfo serviceInfo; @@ -29,11 +23,6 @@ public class BonjourPeer implements Peer { return serviceInfo.getName(); } - @Override - public int getIcon() { - return R.drawable.ic_network_wifi_white; - } - @Override public boolean equals(Object peer) { if (peer != null && peer instanceof BonjourPeer) { @@ -45,12 +34,12 @@ public class BonjourPeer implements Peer { @Override public String getRepoAddress() { - return serviceInfo.getURL(); // Automatically appends the "path" property if present, so no need to do it ourselves. + return serviceInfo.getRepoAddress(); } @Override public String getFingerprint() { - return serviceInfo.getPropertyString("fingerprint"); + return serviceInfo.getFingerprint(); } @Override diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/peers/WifiPeer.java b/F-Droid/src/org/fdroid/fdroid/localrepo/peers/WifiPeer.java new file mode 100644 index 000000000..b1d596f0a --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/peers/WifiPeer.java @@ -0,0 +1,71 @@ +package org.fdroid.fdroid.localrepo.peers; + +import android.net.Uri; +import android.os.Parcel; + +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.data.NewRepoConfig; + +public class WifiPeer implements Peer { + + protected String name; + protected Uri uri; + + public WifiPeer() { + + } + + public WifiPeer(NewRepoConfig config) { + this(config.getRepoUri(), config.getHost()); + } + + protected WifiPeer(Uri uri, String name) { + this.name = name; + this.uri = uri; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getIcon() { + return R.drawable.ic_network_wifi_white; + } + + @Override + public String getRepoAddress() { + return uri.toString(); + } + + @Override + public String getFingerprint() { + return uri.getQueryParameter("fingerprint"); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name); + dest.writeString(uri.toString()); + } + + protected WifiPeer(Parcel in) { + this(Uri.parse(in.readString()), in.readString()); + } + + public static final Creator CREATOR = new Creator() { + public WifiPeer createFromParcel(Parcel source) { + return new WifiPeer(source); + } + + public WifiPeer[] newArray(int size) { + return new WifiPeer[size]; + } + }; +} diff --git a/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java b/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java index bbc731278..f7fb8d7c6 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java +++ b/F-Droid/src/org/fdroid/fdroid/net/LocalHTTPD.java @@ -8,7 +8,7 @@ import android.webkit.MimeTypeMap; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; -import org.fdroid.fdroid.views.swap.ConnectSwapActivity; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; import java.io.File; import java.io.FileInputStream; @@ -77,10 +77,11 @@ public class LocalHTTPD extends NanoHTTPD { 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 intent = new Intent(context, SwapWorkflowActivity.class); intent.setData(repoUri); + intent.putExtra(SwapWorkflowActivity.EXTRA_CONFIRM, true); + intent.putExtra(SwapWorkflowActivity.EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, true); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(ConnectSwapActivity.EXTRA_PREVENT_FURTHER_SWAP_REQUESTS, true); context.startActivity(intent); } 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 70367326f..84f981ca9 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConfirmReceive.java @@ -8,13 +8,18 @@ import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.Menu; import android.view.MenuInflater; +import android.view.View; import android.widget.RelativeLayout; +import android.widget.TextView; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.localrepo.SwapService; public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivity.InnerView { + private NewRepoConfig config; + public ConfirmReceive(Context context) { super(context); } @@ -36,15 +41,23 @@ public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivi return (SwapWorkflowActivity)getContext(); } - private SwapService getManager() { - return getActivity().getState(); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); + findViewById(R.id.no_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getActivity().denySwap(); + } + }); + findViewById(R.id.yes_button).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getActivity().swapWith(config); + } + }); } @Override @@ -71,4 +84,10 @@ public class ConfirmReceive extends RelativeLayout implements SwapWorkflowActivi public String getToolbarTitle() { return getResources().getString(R.string.swap_confirm); } + + public void setup(NewRepoConfig config) { + this.config = config; + TextView descriptionTextView = (TextView) findViewById(R.id.text_description); + descriptionTextView.setText(getResources().getString(R.string.swap_confirm_connect, config.getHost())); + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java deleted file mode 100644 index 4ab1503f8..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java +++ /dev/null @@ -1,175 +0,0 @@ -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.os.Bundle; -import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBarActivity; -import android.util.Log; -import android.view.View; -import android.widget.TextView; - -import org.fdroid.fdroid.ProgressListener; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.UpdateService; -import org.fdroid.fdroid.data.NewRepoConfig; -import org.fdroid.fdroid.data.Repo; -import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.localrepo.SwapService; - -public class ConnectSwapActivity extends ActionBarActivity implements ProgressListener { - - private static final String TAG = "ConnectSwapActivity"; - - /** - * 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"; - - @Nullable - private Repo repo; - - private NewRepoConfig newRepoConfig; - private TextView descriptionTextView; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.swap_confirm_receive); - - descriptionTextView = (TextView) findViewById(R.id.text_description); - - findViewById(R.id.no_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_OK); - finish(); - } - }); - findViewById(R.id.yes_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - confirm(); - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - // Only confirm the action, and then return a result... - newRepoConfig = new NewRepoConfig(this, getIntent()); - if (newRepoConfig.isValidRepo()) { - descriptionTextView.setText(getString(R.string.swap_confirm_connect, newRepoConfig.getHost())); - } 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? - } - } - - @Override - @SuppressWarnings("fallthrough") - public void onProgress(Event event) { - // TODO: Show progress, but we can worry about that later. - // Might be nice to have it nicely embedded in the UI, rather than as - // an additional dialog. E.g. White text on blue, letting the user - // know what we are up to. - - switch (event.type) { - case UpdateService.EVENT_COMPLETE_AND_SAME: - Log.i(TAG, "EVENT_COMPLETE_AND_SAME"); - case UpdateService.EVENT_COMPLETE_WITH_CHANGES: - Log.i(TAG, "EVENT_COMPLETE_WITH_CHANGES"); - Intent intent = new Intent(this, SwapAppListActivity.class); - intent.putExtra(SwapAppListActivity.EXTRA_REPO_ID, repo.getId()); - startActivity(intent); - finish(); - /* - // TODO: Load repo from database to get proper name. This is what the category we want to select will be called. - intent.putExtra("category", newRepoConfig.getHost()); - getActivity().setResult(Activity.RESULT_OK, intent); - */ - break; - case UpdateService.EVENT_ERROR: - // TODO: Show message on this screen (with a big "okay" button that goes back to F-Droid activity) - // rather than finishing directly. - finish(); - break; - } - } - - private void confirm() { - repo = ensureRepoExists(); - if (repo != null) { - UpdateService.updateRepoNow(repo.address, this).setListener(this); - } - } - - private Repo ensureRepoExists() { - 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(this, 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, 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); - values.put(RepoProvider.DataColumns.IS_SWAP, true); - Uri uri = RepoProvider.Helper.insert(this, values); - repo = RepoProvider.Helper.findByUri(this, uri); - } - - 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.isValidRepo() || newRepoConfig.preventFurtherSwaps()) { - return; - } - - ServiceConnection connection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - SwapService service = ((SwapService.Binder) binder).getService(); - if (service.isEnabled()) { - service.askServerToSwapWithUs(newRepoConfig); - } - unbindService(this); - } - - @Override - public void onServiceDisconnected(ComponentName name) {} - }; - - Intent intent = new Intent(this, SwapService.class); - bindService(intent, connection, BIND_AUTO_CREATE); - } -} 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 263ab4a01..bbf774896 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -27,7 +27,6 @@ import android.widget.Toast; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; -import org.fdroid.fdroid.FDroid; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.NfcHelper; import org.fdroid.fdroid.Preferences; @@ -44,6 +43,16 @@ import java.util.Set; public class SwapWorkflowActivity extends AppCompatActivity { + /** + * 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"; + public static final String EXTRA_CONFIRM = "EXTRA_CONFIRM"; + private ViewGroup container; /** @@ -73,6 +82,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { private InnerView currentView; private boolean hasPreparedLocalRepo = false; private PrepareSwapRepo updateSwappableAppsTask = null; + private NewRepoConfig confirmSwapConfig = null; @NonNull private final ServiceConnection serviceConnection = new ServiceConnection() { @@ -152,9 +162,20 @@ public class SwapWorkflowActivity extends AppCompatActivity { @Override protected void onResume() { super.onResume(); + + checkIncomingIntent(); showRelevantView(); } + private void checkIncomingIntent() { + Intent intent = getIntent(); + if (intent.getBooleanExtra(EXTRA_CONFIRM, false)) { + // Storing config in this variable will ensure that when showRelevantView() is next + // run, it will show the connect swap view (if the service is available). + confirmSwapConfig = new NewRepoConfig(this, intent); + } + } + private void showRelevantView() { if (service == null) { @@ -162,6 +183,15 @@ public class SwapWorkflowActivity extends AppCompatActivity { return; } + // This is separate from the switch statement below, because it is usually populated + // during onResume, when there is a high probability of not having a swap service + // available. Thus, we were unable to set the state of the swap service appropriately. + if (confirmSwapConfig != null) { + showConfirmSwap(confirmSwapConfig); + confirmSwapConfig = null; + return; + } + if (container.getVisibility() == View.GONE || currentView != null && currentView.getStep() == service.getStep()) { // Already showing the correct step, so don't bother changing anything. return; @@ -196,7 +226,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { } } - private void inflateInnerView(@LayoutRes int viewRes) { + private InnerView inflateInnerView(@LayoutRes int viewRes) { container.removeAllViews(); View view = ((LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(viewRes, container, false); currentView = (InnerView)view; @@ -221,6 +251,8 @@ public class SwapWorkflowActivity extends AppCompatActivity { }); container.addView(view); supportInvalidateOptionsMenu(); + + return currentView; } private void onToolbarCancel() { @@ -240,6 +272,10 @@ public class SwapWorkflowActivity extends AppCompatActivity { inflateInnerView(R.layout.swap_blank); } + private void showConfirmSwap(@NonNull NewRepoConfig config) { + ((ConfirmReceive)inflateInnerView(R.layout.swap_confirm_receive)).setup(config); + } + public void showSelectApps() { inflateInnerView(R.layout.swap_select_apps); } @@ -331,6 +367,23 @@ public class SwapWorkflowActivity extends AppCompatActivity { showSelectApps(); } + /** + * When swapping with a peer that is identified by a NewRepoConfig, that means that they + * came from a QR Code scan. In this situation, we should already have a full swap repo + * ready to go. + * TODO: What if we scanned the repo but do not have a repo running yet? + */ + public void swapWith(NewRepoConfig repoConfig) { + getService().swapWith(repoConfig.toPeer()); + if (!repoConfig.preventFurtherSwaps()) { + startSwappingWithPeer(); + } + } + + public void denySwap() { + showIntro(); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); @@ -338,7 +391,8 @@ public class SwapWorkflowActivity extends AppCompatActivity { if (scanResult.getContents() != null) { NewRepoConfig repoConfig = new NewRepoConfig(this, scanResult.getContents()); if (repoConfig.isValidRepo()) { - startActivityForResult(new Intent(FDroid.ACTION_ADD_REPO, Uri.parse(scanResult.getContents()), this, ConnectSwapActivity.class), CONNECT_TO_SWAP); + confirmSwapConfig = repoConfig; + showRelevantView(); } else { Toast.makeText(this, "The QR code you scanned doesn't look like a swap code.", Toast.LENGTH_SHORT).show(); }