Index: res/values-it/strings.xml =================================================================== --- res/values-it/strings.xml +++ res/values-it/strings.xml @@ -5,6 +5,10 @@ Stai utilizzando l\'ultimo ritrovato in materia di rispetto della tua privacy. Cerca QR Code + Si + No + Installare Barcode Scanner? + Questa azione richiede un\'altra app per scansionare i codici QR. Vuoi installare Barcode Scanner? Numero fermata Nome fermata Inserisci il numero della fermata @@ -21,7 +25,7 @@ Linee: %1$s Scegli la fermata… Nessun passaggio - Nessun QR code + Nessun QR code trovato, prova ad usare un\'altra app Preferiti Aiuto Informazioni Index: res/values/strings.xml =================================================================== --- res/values/strings.xml +++ res/values/strings.xml @@ -8,6 +8,11 @@ Search Scan QR Code + Yes + No + Install Barcode Scanner? + This application requires an app to scan the QR codes. Would you like to install Barcode Scanner now? + Bus stop number Bus stop name Insert bus stop number @@ -27,7 +32,7 @@ Lines: %1$s Line: %1$s No timetable found - No QR code + No QR code found, try using another app to scan Unexpected internal error, cannot extract data from GTT/5T website Help About Index: src/com/google/zxing/integration/android/IntentIntegrator.java =================================================================== --- src/com/google/zxing/integration/android/IntentIntegrator.java +++ src/com/google/zxing/integration/android/IntentIntegrator.java @@ -347,6 +347,7 @@ PackageManager pm = activity.getPackageManager(); List availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (availableApps != null) { + Log.d("IntentIntegrator","Available app to scan QR Code: "+availableApps); for (String targetApp : targetApplications) { if (contains(availableApps, targetApp)) { return targetApp; Index: src/it/reyboz/bustorino/fragments/MainScreenFragment.java =================================================================== --- src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -3,9 +3,11 @@ import android.Manifest; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.location.Criteria; import android.location.Location; +import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -38,7 +40,6 @@ import android.widget.Toast; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.zxing.integration.android.IntentIntegrator; import java.util.Map; @@ -47,9 +48,13 @@ import it.reyboz.bustorino.middleware.AppLocationManager; import it.reyboz.bustorino.middleware.AsyncArrivalsSearcher; import it.reyboz.bustorino.middleware.AsyncStopsSearcher; +import it.reyboz.bustorino.middleware.BarcodeScanContract; +import it.reyboz.bustorino.middleware.BarcodeScanOptions; +import it.reyboz.bustorino.middleware.BarcodeScanUtils; import it.reyboz.bustorino.util.LocationCriteria; import it.reyboz.bustorino.util.Permissions; +import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri; import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS; import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN; @@ -117,6 +122,34 @@ new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute(); } }; + // + private final ActivityResultLauncher barcodeLauncher = registerForActivityResult(new BarcodeScanContract(), + result -> { + if(result!=null && result.getContents()!=null) { + //Toast.makeText(MyActivity.this, "Cancelled", Toast.LENGTH_LONG).show(); + Uri uri; + try { + uri = Uri.parse(result.getContents()); // this apparently prevents NullPointerException. Somehow. + } catch (NullPointerException e) { + if (getContext()!=null) + Toast.makeText(getContext().getApplicationContext(), + R.string.no_qrcode, Toast.LENGTH_SHORT).show(); + return; + } + String busStopID = getBusStopIDFromUri(uri); + busStopSearchByIDEditText.setText(busStopID); + requestArrivalsForStopID(busStopID); + + } else { + //Toast.makeText(MyActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show(); + if (getContext()!=null) + Toast.makeText(getContext().getApplicationContext(), + R.string.no_qrcode, Toast.LENGTH_SHORT).show(); + + + + } + }); /// LOCATION STUFF /// boolean pendingNearbyStopsRequest = false; @@ -454,8 +487,14 @@ * @param v View QRButton clicked */ public void onQRButtonClick(View v) { - IntentIntegrator integrator = new IntentIntegrator(getActivity()); - integrator.initiateScan(); + + BarcodeScanOptions scanOptions = new BarcodeScanOptions(); + Intent intent = scanOptions.createScanIntent(); + if(!BarcodeScanUtils.checkTargetPackageExists(getContext(), intent)){ + BarcodeScanUtils.showDownloadDialog(null, this); + }else { + barcodeLauncher.launch(scanOptions); + } } public void onHideHint(View v) { Index: src/it/reyboz/bustorino/middleware/BarcodeScanContract.java =================================================================== --- /dev/null +++ src/it/reyboz/bustorino/middleware/BarcodeScanContract.java @@ -0,0 +1,33 @@ +/* + * Based on ZXing Android Embedded, Copyright 2021 ZXing Android Embedded authors. + + */ + +package it.reyboz.bustorino.middleware; + +import android.content.Context; +import android.content.Intent; + +import androidx.activity.result.contract.ActivityResultContract; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.zxing.integration.android.IntentIntegrator; +import com.google.zxing.integration.android.IntentResult; + +public class BarcodeScanContract extends ActivityResultContract { + + @NonNull + @Override + public Intent createIntent(@NonNull Context context, BarcodeScanOptions input) { + return input.createScanIntent(); + } + + + @Override + public IntentResult parseResult(int resultCode, @Nullable Intent intent) { + return IntentIntegrator.parseActivityResult(IntentIntegrator.REQUEST_CODE, resultCode, intent); + } + + +} Index: src/it/reyboz/bustorino/middleware/BarcodeScanOptions.java =================================================================== --- /dev/null +++ src/it/reyboz/bustorino/middleware/BarcodeScanOptions.java @@ -0,0 +1,172 @@ + +/* + * Based on ZXing Android Embedded, Copyright 2021 ZXing Android Embedded authors. + + */ +package it.reyboz.bustorino.middleware; + +import android.content.Intent; +import android.os.Bundle; + + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BarcodeScanOptions { + + public static final String BS_PACKAGE = "com.google.zxing.client.android"; + + // supported barcode formats + + // Product Codes + public static final String UPC_A = "UPC_A"; + public static final String UPC_E = "UPC_E"; + public static final String EAN_8 = "EAN_8"; + public static final String EAN_13 = "EAN_13"; + public static final String RSS_14 = "RSS_14"; + + // Other 1D + public static final String CODE_39 = "CODE_39"; + public static final String CODE_93 = "CODE_93"; + public static final String CODE_128 = "CODE_128"; + public static final String ITF = "ITF"; + + public static final String RSS_EXPANDED = "RSS_EXPANDED"; + + // 2D + public static final String QR_CODE = "QR_CODE"; + public static final String DATA_MATRIX = "DATA_MATRIX"; + public static final String PDF_417 = "PDF_417"; + + + public static final Collection PRODUCT_CODE_TYPES = list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14); + public static final Collection ONE_D_CODE_TYPES = + list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14, CODE_39, CODE_93, CODE_128, + ITF, RSS_14, RSS_EXPANDED); + + public static final Collection ALL_CODE_TYPES = null; + + private final Map moreExtras = new HashMap<>(3); + + private Collection desiredBarcodeFormats; + + + private int cameraId = 0; + + public BarcodeScanOptions() { + + } + + public Map getMoreExtras() { + return moreExtras; + } + + public final BarcodeScanOptions addExtra(String key, Object value) { + moreExtras.put(key, value); + return this; + } + + public final BarcodeScanOptions setCameraID(int cameraID){ + this.cameraId = cameraID; + return this; + } + + /** + * Set the desired barcode formats to scan. + * + * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for + * @return this + */ + public BarcodeScanOptions setDesiredBarcodeFormats(Collection desiredBarcodeFormats) { + this.desiredBarcodeFormats = desiredBarcodeFormats; + return this; + } + + /** + * Set the desired barcode formats to scan. + * + * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for + * @return this + */ + public BarcodeScanOptions setDesiredBarcodeFormats(String... desiredBarcodeFormats) { + this.desiredBarcodeFormats = Arrays.asList(desiredBarcodeFormats); + return this; + } + + + /** + * Create an scan intent with the specified options. + * + * @return the intent + */ + public Intent createScanIntent() { + Intent intentScan = new Intent(BS_PACKAGE + ".SCAN"); + intentScan.addCategory(Intent.CATEGORY_DEFAULT); + + // check which types of codes to scan for + if (desiredBarcodeFormats != null) { + // set the desired barcode types + StringBuilder joinedByComma = new StringBuilder(); + for (String format : desiredBarcodeFormats) { + if (joinedByComma.length() > 0) { + joinedByComma.append(','); + } + joinedByComma.append(format); + } + intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString()); + } + + // check requested camera ID + if (cameraId >= 0) { + intentScan.putExtra("SCAN_CAMERA_ID", cameraId); + } + + intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + attachMoreExtras(intentScan); + return intentScan; + } + + private static List list(String... values) { + return Collections.unmodifiableList(Arrays.asList(values)); + } + + private void attachMoreExtras(Intent intent) { + for (Map.Entry entry : moreExtras.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // Kind of hacky + if (value instanceof Integer) { + intent.putExtra(key, (Integer) value); + } else if (value instanceof Long) { + intent.putExtra(key, (Long) value); + } else if (value instanceof Boolean) { + intent.putExtra(key, (Boolean) value); + } else if (value instanceof Double) { + intent.putExtra(key, (Double) value); + } else if (value instanceof Float) { + intent.putExtra(key, (Float) value); + } else if (value instanceof Bundle) { + intent.putExtra(key, (Bundle) value); + } else if (value instanceof int[]) { + intent.putExtra(key, (int[]) value); + } else if (value instanceof long[]) { + intent.putExtra(key, (long[]) value); + } else if (value instanceof boolean[]) { + intent.putExtra(key, (boolean[]) value); + } else if (value instanceof double[]) { + intent.putExtra(key, (double[]) value); + } else if (value instanceof float[]) { + intent.putExtra(key, (float[]) value); + } else if (value instanceof String[]) { + intent.putExtra(key, (String[]) value); + } else { + intent.putExtra(key, value.toString()); + } + } + } +} \ No newline at end of file Index: src/it/reyboz/bustorino/middleware/BarcodeScanUtils.java =================================================================== --- /dev/null +++ src/it/reyboz/bustorino/middleware/BarcodeScanUtils.java @@ -0,0 +1,62 @@ +package it.reyboz.bustorino.middleware; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import java.util.List; + +import it.reyboz.bustorino.R; + +public class BarcodeScanUtils { + + + public static boolean checkTargetPackageExists(Context context,Intent intent) { + PackageManager pm = context.getPackageManager(); + List availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + if (availableApps != null) { + return !availableApps.isEmpty(); + } + return false; + } + + public static AlertDialog showDownloadDialog(@Nullable Activity activity,@Nullable final Fragment fragment) { + if (activity == null){ + if (fragment==null) throw new IllegalArgumentException("Cannot put both activity and fragment null"); + activity = fragment.getActivity(); + } + AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); + downloadDialog.setTitle(R.string.title_barcode_scanner_install); + downloadDialog.setMessage(R.string.message_install_barcode_scanner); + final Activity finalActivity = activity; + downloadDialog.setPositiveButton(R.string.yes, (dialogInterface, i) -> { + final String packageName = BarcodeScanOptions.BS_PACKAGE; + Uri uri = Uri.parse("market://details?id=" + packageName); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + try { + if (fragment == null) { + finalActivity.startActivity(intent); + } else { + fragment.startActivity(intent); + } + } catch (ActivityNotFoundException anfe) { + // Hmm, market is not installed + Log.w("BusTO-BarcodeScanUtils", "Google Play is not installed; cannot install " + packageName); + } + }); + downloadDialog.setNegativeButton(R.string.no, null); + downloadDialog.setCancelable(true); + return downloadDialog.show(); + } +} Index: src/it/reyboz/bustorino/middleware/GeneralActivity.java =================================================================== --- src/it/reyboz/bustorino/middleware/GeneralActivity.java +++ src/it/reyboz/bustorino/middleware/GeneralActivity.java @@ -1,3 +1,20 @@ +/* + BusTO - Arrival times for Turin public transport. + Copyright (C) 2021 Fabio Mazza + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ package it.reyboz.bustorino.middleware; import android.Manifest;