diff --git a/res/layout/swap_wifi_qr.xml b/res/layout/swap_wifi_qr.xml
index 8d67d687c..b916b0332 100644
--- a/res/layout/swap_wifi_qr.xml
+++ b/res/layout/swap_wifi_qr.xml
@@ -24,22 +24,26 @@
android:layout_below="@+id/textView"
android:layout_above="@+id/device_ip_address"/>
+
+ android:layout_alignParentBottom="true"
+ android:id="@+id/btn_qr_scanner"/>
+
+
A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple + * way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the + * project's source code.
+ * + *To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait + * for the result in your app.
+ * + *It does require that the Barcode Scanner (or work-alike) application is installed. The + * {@link #initiateScan()} method will prompt the user to download the application, if needed.
+ * + *There are a few steps to using this integration. First, your {@link Activity} must implement + * the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:
+ * + *{@code + * public void onActivityResult(int requestCode, int resultCode, Intent intent) { + * IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + * if (scanResult != null) { + * // handle scan result + * } + * // else continue with any other code you need in the method + * ... + * } + * }+ * + *
This is where you will handle a scan result.
+ * + *Second, just call this in response to a user action somewhere to begin the scan process:
+ * + *{@code + * IntentIntegrator integrator = new IntentIntegrator(yourActivity); + * integrator.initiateScan(); + * }+ * + *
Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the + * user was prompted to download the application. This lets the calling app potentially manage the dialog. + * In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()} + * method.
+ * + *You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use + * {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and + * yes/no button labels can be changed.
+ * + *Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used + * to invoke the scanner. This can be used to set additional options not directly exposed by this + * simplified API.
+ * + *By default, this will only allow applications that are known to respond to this intent correctly + * do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}. + * For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.
+ * + *To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.
+ * + *Some code, particularly download integration, was contributed from the Anobiit application.
+ * + *Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as + * PDF417. Use {@link #initiateScan(java.util.Collection)} with + * a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such + * formats.
+ * + * @author Sean Owen + * @author Fred Lin + * @author Isaac Potoczny-Jones + * @author Brad Drehmer + * @author gcstang + */ +public class IntentIntegrator { + + public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits + private static final String TAG = IntentIntegrator.class.getSimpleName(); + + public static final String DEFAULT_TITLE = "Install Barcode Scanner?"; + public static final String DEFAULT_MESSAGE = + "This application requires Barcode Scanner. Would you like to install it?"; + public static final String DEFAULT_YES = "Yes"; + public static final String DEFAULT_NO = "No"; + + private static final String BS_PACKAGE = "com.google.zxing.client.android"; + private static final String BSPLUS_PACKAGE = "com.srowen.bs.android"; + + // supported barcode formats + public static final CollectionCall this from your {@link Activity}'s + * {@link Activity#onActivityResult(int, int, Intent)} method.
+ * + * @param requestCode request code from {@code onActivityResult()} + * @param resultCode result code from {@code onActivityResult()} + * @param intent {@link Intent} from {@code onActivityResult()} + * @return null if the event handled here was not related to this class, or + * else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning, + * the fields will be null. + */ + public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == REQUEST_CODE) { + if (resultCode == Activity.RESULT_OK) { + String contents = intent.getStringExtra("SCAN_RESULT"); + String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT"); + byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES"); + int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE); + Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation; + String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL"); + return new IntentResult(contents, + formatName, + rawBytes, + orientation, + errorCorrectionLevel); + } + return new IntentResult(); + } + return null; + } + + + /** + * Defaults to type "TEXT_TYPE". + * + * @param text the text string to encode as a barcode + * @return the {@link AlertDialog} that was shown to the user prompting them to download the app + * if a prompt was needed, or null otherwise + * @see #shareText(CharSequence, CharSequence) + */ + public final AlertDialog shareText(CharSequence text) { + return shareText(text, "TEXT_TYPE"); + } + + /** + * Shares the given text by encoding it as a barcode, such that another user can + * scan the text off the screen of the device. + * + * @param text the text string to encode as a barcode + * @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants. + * @return the {@link AlertDialog} that was shown to the user prompting them to download the app + * if a prompt was needed, or null otherwise + */ + public final AlertDialog shareText(CharSequence text, CharSequence type) { + Intent intent = new Intent(); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setAction(BS_PACKAGE + ".ENCODE"); + intent.putExtra("ENCODE_TYPE", type); + intent.putExtra("ENCODE_DATA", text); + String targetAppPackage = findTargetAppPackage(intent); + if (targetAppPackage == null) { + return showDownloadDialog(); + } + intent.setPackage(targetAppPackage); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + attachMoreExtras(intent); + if (fragment == null) { + activity.startActivity(intent); + } else { + fragment.startActivity(intent); + } + return null; + } + + private static ListEncapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.
+ * + * @author Sean Owen + */ +public final class IntentResult { + + private final String contents; + private final String formatName; + private final byte[] rawBytes; + private final Integer orientation; + private final String errorCorrectionLevel; + + IntentResult() { + this(null, null, null, null, null); + } + + IntentResult(String contents, + String formatName, + byte[] rawBytes, + Integer orientation, + String errorCorrectionLevel) { + this.contents = contents; + this.formatName = formatName; + this.rawBytes = rawBytes; + this.orientation = orientation; + this.errorCorrectionLevel = errorCorrectionLevel; + } + + /** + * @return raw content of barcode + */ + public String getContents() { + return contents; + } + + /** + * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names. + */ + public String getFormatName() { + return formatName; + } + + /** + * @return raw bytes of the barcode content, if applicable, or null otherwise + */ + public byte[] getRawBytes() { + return rawBytes; + } + + /** + * @return rotation of the image, in degrees, which resulted in a successful scan. May be null. + */ + public Integer getOrientation() { + return orientation; + } + + /** + * @return name of the error correction level used in the barcode, if applicable + */ + public String getErrorCorrectionLevel() { + return errorCorrectionLevel; + } + + @Override + public String toString() { + StringBuilder dialogText = new StringBuilder(100); + dialogText.append("Format: ").append(formatName).append('\n'); + dialogText.append("Contents: ").append(contents).append('\n'); + int rawBytesLength = rawBytes == null ? 0 : rawBytes.length; + dialogText.append("Raw bytes: (").append(rawBytesLength).append(" bytes)\n"); + dialogText.append("Orientation: ").append(orientation).append('\n'); + dialogText.append("EC level: ").append(errorCorrectionLevel).append('\n'); + return dialogText.toString(); + } + +} diff --git a/src/org/fdroid/fdroid/data/NewRepoConfig.java b/src/org/fdroid/fdroid/data/NewRepoConfig.java index 080c97b4a..55cfb5348 100644 --- a/src/org/fdroid/fdroid/data/NewRepoConfig.java +++ b/src/org/fdroid/fdroid/data/NewRepoConfig.java @@ -26,16 +26,24 @@ public class NewRepoConfig { private String bssid; private String ssid; + public NewRepoConfig(Context context, String uri) { + init(context, Uri.parse(uri)); + } + public NewRepoConfig(Context context, Intent intent) { + init(context, intent.getData()); + } + + private void init(Context context, Uri incomingUri) { /* an URL from a click, NFC, QRCode scan, etc */ - uri = intent.getData(); + uri = incomingUri; if (uri == null) { isValidRepo = false; return; } // scheme and host should only ever be pure ASCII aka Locale.ENGLISH - scheme = intent.getScheme(); + scheme = uri.getScheme(); host = uri.getHost(); port = uri.getPort(); if (TextUtils.isEmpty(scheme) || TextUtils.isEmpty(host)) { diff --git a/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java b/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java index 77b4ebc16..2e01cbbbe 100644 --- a/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java +++ b/src/org/fdroid/fdroid/views/swap/ConfirmReceiveSwapFragment.java @@ -44,6 +44,7 @@ public class ConfirmReceiveSwapFragment extends Fragment implements ProgressList } private void finish() { + getActivity().setResult(Activity.RESULT_OK); getActivity().finish(); } diff --git a/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java b/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java index a7138cd74..34c67fef2 100644 --- a/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java +++ b/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java @@ -1,6 +1,7 @@ package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -19,17 +20,24 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +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.Preferences; import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.net.WifiStateChangeService; import java.util.Locale; public class WifiQrFragment extends Fragment { + private static final int CONNECT_TO_SWAP = 1; + private BroadcastReceiver onWifiChange = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { @@ -45,19 +53,33 @@ public class WifiQrFragment extends Fragment { // Replace all blacks with the background blue. qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue))); - Button openQr = (Button)view.findViewById(R.id.button); + Button openQr = (Button)view.findViewById(R.id.btn_qr_scanner); openQr.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { - // TODO: What is the "correct" intent for starting barcode scanner? - // I realise that google stuffed up by not standardising this, so - // not quite sure the best way to proceed. + IntentIntegrator integrator = new IntentIntegrator(WifiQrFragment.this); + integrator.initiateScan(); } }); return view; } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); + if (scanResult != null) { + NewRepoConfig repoConfig = new NewRepoConfig(getActivity(), scanResult.getContents()); + if (repoConfig.isValidRepo()) { + startActivityForResult(new Intent(FDroid.ACTION_ADD_REPO, Uri.parse(scanResult.getContents()), getActivity(), ConnectSwapActivity.class), CONNECT_TO_SWAP); + } else { + Toast.makeText(getActivity(), "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) { + getActivity().finish(); + } + } + public void onResume() { super.onResume(); setUIFromWifi();