diff --git a/F-Droid/res/layout/swap_wifi_qr.xml b/F-Droid/res/layout/swap_wifi_qr.xml index 44967077a..3e461aeaa 100644 --- a/F-Droid/res/layout/swap_wifi_qr.xml +++ b/F-Droid/res/layout/swap_wifi_qr.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView +<org.fdroid.fdroid.views.swap.views.WifiQrView xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="match_parent" @@ -50,4 +50,4 @@ </LinearLayout> -</ScrollView> \ No newline at end of file +</org.fdroid.fdroid.views.swap.views.WifiQrView> \ No newline at end of file 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 8580e1b2d..f8fc37ace 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -1,7 +1,9 @@ package org.fdroid.fdroid.views.swap; +import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -15,11 +17,16 @@ import android.view.View; import android.view.ViewGroup; 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; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.localrepo.LocalRepoManager; import java.util.Set; @@ -39,6 +46,8 @@ public class SwapWorkflowActivity extends FragmentActivity { boolean buildMenu(Menu menu, @NonNull MenuInflater inflater); } + private static final int CONNECT_TO_SWAP = 1; + private State currentState = State.INTRO; private InnerView currentView; private boolean hasPreparedLocalRepo = false; @@ -115,10 +124,14 @@ public class SwapWorkflowActivity extends FragmentActivity { public void onJoinWifiComplete() { ensureLocalRepoRunning(); if (!attemptToShowNfc()) { - // showWifiQr(); + showWifiQr(); } } + public void showWifiQr() { + inflateInnerView(R.layout.swap_wifi_qr); + } + private boolean attemptToShowNfc() { // TODO: What if NFC is disabled? Hook up with NfcNotEnabledActivity? Or maybe only if they // click a relevant button? @@ -169,6 +182,23 @@ public class SwapWorkflowActivity extends FragmentActivity { finish(); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + if (scanResult != null) { + 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); + } else { + Toast.makeText(this, "The QR code you scanned doesn't look like a swap code.", Toast.LENGTH_SHORT).show(); + } + } + } else if (requestCode == CONNECT_TO_SWAP && resultCode == Activity.RESULT_OK) { + finish(); + } + } + class UpdateAsyncTask extends AsyncTask<Void, String, Void> { @SuppressWarnings("UnusedDeclaration") diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java index 8faf61da3..63ce23c61 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java @@ -36,6 +36,11 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne super(context, attrs, defStyleAttr, defStyleRes); } + private SwapWorkflowActivity getActivity() { + // TODO: Try and find a better way to get to the SwapActivity, which makes less asumptions. + return (SwapWorkflowActivity)getContext(); + } + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -56,7 +61,7 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne next.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - // TODO: Show QR Code. + getActivity().showWifiQr(); return true; } }); diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java new file mode 100644 index 000000000..09b5d66c6 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java @@ -0,0 +1,159 @@ +package org.fdroid.fdroid.views.swap.views; + +import android.annotation.TargetApi; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.LightingColorFilter; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.google.zxing.integration.android.IntentIntegrator; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.QrGenAsyncTask; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.net.WifiStateChangeService; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; + +import java.net.URI; +import java.util.List; +import java.util.Locale; + +public class WifiQrView extends ScrollView implements SwapWorkflowActivity.InnerView { + + private static final String TAG = "WifiQrView"; + + public WifiQrView(Context context) { + super(context); + } + + public WifiQrView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public WifiQrView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WifiQrView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + private SwapWorkflowActivity getActivity() { + // TODO: Try and find a better way to get to the SwapActivity, which makes less asumptions. + return (SwapWorkflowActivity)getContext(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + setUIFromWifi(); + + ImageView qrImage = (ImageView)findViewById(R.id.wifi_qr_code); + + // Replace all blacks with the background blue. + qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue))); + + Button openQr = (Button)findViewById(R.id.btn_qr_scanner); + openQr.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) { + IntentIntegrator integrator = new IntentIntegrator(getActivity()); + integrator.initiateScan(); + } + }); + + Button cancel = (Button)findViewById(R.id.btn_cancel_swap); + cancel.setOnClickListener(new Button.OnClickListener() { + @Override + public void onClick(View v) { + getActivity().stopSwapping(); + } + }); + + LocalBroadcastManager.getInstance(getActivity()).registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + setUIFromWifi(); + } + }, + new IntentFilter(WifiStateChangeService.BROADCAST) + ); + } + + @Override + public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) { + return false; + } + + private void setUIFromWifi() { + + if (TextUtils.isEmpty(FDroidApp.repo.address)) + return; + + String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://"; + + // the fingerprint is not useful on the button label + String buttonLabel = scheme + FDroidApp.ipAddressString + ":" + FDroidApp.port; + TextView ipAddressView = (TextView) findViewById(R.id.device_ip_address); + ipAddressView.setText(buttonLabel); + + /* + * Set URL to UPPER for compact QR Code, FDroid will translate it back. + * Remove the SSID from the query string since SSIDs are case-sensitive. + * Instead the receiver will have to rely on the BSSID to find the right + * wifi AP to join. Lots of QR Scanners are buggy and do not respect + * custom URI schemes, so we have to use http:// or https:// :-( + */ + Uri sharingUri = Utils.getSharingUri(FDroidApp.repo); + String qrUriString = (scheme + sharingUri.getHost()).toUpperCase(Locale.ENGLISH); + if (sharingUri.getPort() != 80) { + qrUriString += ":" + sharingUri.getPort(); + } + qrUriString += sharingUri.getPath().toUpperCase(Locale.ENGLISH); + boolean first = true; + + // Andorid provides an API for getting the query parameters and iterating over them: + // Uri.getQueryParameterNames() + // But it is only available on later Android versions. As such we use URLEncodedUtils instead. + List<NameValuePair> parameters = URLEncodedUtils.parse(URI.create(sharingUri.toString()), "UTF-8"); + for (NameValuePair parameter : parameters) { + if (!parameter.getName().equals("ssid")) { + if (first) { + qrUriString += "?"; + first = false; + } else { + qrUriString += "&"; + } + qrUriString += parameter.getName().toUpperCase(Locale.ENGLISH) + "=" + + parameter.getValue().toUpperCase(Locale.ENGLISH); + } + } + + Log.i(TAG, "Encoded swap URI in QR Code: " + qrUriString); + + new QrGenAsyncTask(getActivity(), R.id.wifi_qr_code).execute(qrUriString); + + } + +}