Index: res/values-it/strings.xml
===================================================================
--- res/values-it/strings.xml
+++ res/values-it/strings.xml
@@ -5,6 +5,10 @@
     <string name="app_description">Stai utilizzando l\'ultimo ritrovato in materia di rispetto della tua privacy.</string>
     <string name="search">Cerca</string>
     <string name="qrcode">QR Code</string>
+    <string name="yes">Si</string>
+    <string name="no">No</string>
+    <string name="title_barcode_scanner_install">Installare Barcode Scanner?</string>
+    <string name="message_install_barcode_scanner">Questa azione richiede un\'altra app per scansionare i codici QR. Vuoi installare Barcode Scanner?</string>
     <string name="insert_bus_stop_number">Numero fermata</string>
     <string name="insert_bus_stop_name">Nome fermata</string>
     <string name="insert_bus_stop_number_error">Inserisci il numero della fermata</string>
@@ -21,7 +25,7 @@
     <string name="lines_fill">Linee: %1$s</string>
     <string name="results">Scegli la fermata…</string>
     <string name="no_passages">Nessun passaggio</string>
-    <string name="no_qrcode">Nessun QR code</string>
+    <string name="no_qrcode">Nessun QR code trovato, prova ad usare un\'altra app</string>
     <string name="action_favorites">Preferiti</string>
     <string name="action_help">Aiuto</string>
     <string name="action_about">Informazioni</string>
Index: res/values/strings.xml
===================================================================
--- res/values/strings.xml
+++ res/values/strings.xml
@@ -8,6 +8,11 @@
     </string>
     <string name="search">Search</string>
     <string name="qrcode">Scan QR Code</string>
+    <string name="yes">Yes</string>
+    <string name="no">No</string>
+    <string name="title_barcode_scanner_install">Install Barcode Scanner?</string>
+    <string name="message_install_barcode_scanner">This application requires an app to scan the QR codes. Would you like to install Barcode Scanner now?</string>
+
     <string name="insert_bus_stop_number">Bus stop number</string>
     <string name="insert_bus_stop_name">Bus stop name</string>
     <string name="insert_bus_stop_number_error">Insert bus stop number</string>
@@ -27,7 +32,7 @@
     <string name="lines_fill">Lines: %1$s</string>
     <string name="line_fill">Line: %1$s</string>
     <string name="no_passages">No timetable found</string>
-    <string name="no_qrcode">No QR code</string>
+    <string name="no_qrcode">No QR code found, try using another app to scan</string>
     <string name="internal_error">Unexpected internal error, cannot extract data from GTT/5T website</string>
     <string name="action_help">Help</string>
     <string name="action_about">About</string>
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<ResolveInfo> 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<BarcodeScanOptions> 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<BarcodeScanOptions, IntentResult> {
+
+    @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<String> PRODUCT_CODE_TYPES = list(UPC_A, UPC_E, EAN_8, EAN_13, RSS_14);
+    public static final Collection<String> 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<String> ALL_CODE_TYPES = null;
+
+    private final Map<String, Object> moreExtras = new HashMap<>(3);
+
+    private Collection<String> desiredBarcodeFormats;
+
+
+    private int cameraId = 0;
+
+    public BarcodeScanOptions() {
+
+    }
+
+    public Map<String, ?> 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<String> 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<String> list(String... values) {
+        return Collections.unmodifiableList(Arrays.asList(values));
+    }
+
+    private void attachMoreExtras(Intent intent) {
+        for (Map.Entry<String, Object> 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<ResolveInfo> 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 <http://www.gnu.org/licenses/>.
+ */
 package it.reyboz.bustorino.middleware;
 
 import android.Manifest;