Removed ConnectSwapActivity, refactored into SwapWorkflowActivity.

Involved creating another view/state for which the swap workflow can
be in. It is not explicitly stated by setting the state of the SwapService,
as is the case with other views. Rather, it is inferred based on the
presence of a `NewRepoConfig` crafted from the incoming intent in
`onResume()`.

Also gave me an idea of how to move more logic out of individual views,
and into the SwapWorkflowActivity. That is, inflateInnerView should
return the inflated view, to be cast into the specific subclass. From
there, the activity can call methods directly on the view to set it
up, rather than having the view do that stuff itself. In the future,
may consider doing this with other views too.
This commit is contained in:
Peter Serwylo 2015-07-12 22:13:15 +10:00
parent 4e4d3ec3f9
commit f236dc3d3e
11 changed files with 188 additions and 247 deletions

View File

@ -298,18 +298,6 @@
android:name="android.app.default_searchable"
android:value=".SearchResults" />
</activity>
<activity
android:name=".views.swap.ConnectSwapActivity"
android:theme="@style/SwapTheme.Wizard.ReceiveSwap"
android:label=""
android:noHistory="true"
android:parentActivityName=".FDroid"
android:screenOrientation="portrait"
android:configChanges="orientation|keyboardHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".FDroid" />
</activity>
<activity
android:name=".views.ManageReposActivity"
android:label="@string/app_name"

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<org.fdroid.fdroid.views.swap.ConfirmReceive
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/swap_blue"
android:padding="18dp">
<org.fdroid.fdroid.views.swap.ConfirmReceive xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/swap_blue"
android:padding="18dp">
<!-- Padding is 32px * 0.56 = 18dip -->
<ImageView
@ -34,7 +35,7 @@
<TextView
android:id="@+id/text_description"
android:text="@string/swap_confirm_connect"
tools:text="@string/swap_confirm_connect"
style="@style/SwapTheme.Wizard.Text"
android:layout_below="@id/text_title"
android:layout_width="match_parent"
@ -55,7 +56,7 @@
<Button
android:id="@+id/no_button"
android:text="@string/no"
android:background="@color/swap_deny"
app:backgroundTint="@color/swap_deny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="25dp"
@ -66,7 +67,7 @@
<Button
android:id="@+id/yes_button"
android:text="@string/yes"
android:background="@color/swap_confirm"
app:backgroundTint="@color/swap_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

View File

@ -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);

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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

View File

@ -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<WifiPeer> CREATOR = new Creator<WifiPeer>() {
public WifiPeer createFromParcel(Parcel source) {
return new WifiPeer(source);
}
public WifiPeer[] newArray(int size) {
return new WifiPeer[size];
}
};
}

View File

@ -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);
}

View File

@ -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()));
}
}

View File

@ -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);
}
}

View File

@ -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();
}