|
| 1 | +package com.nlinterface.viewmodels |
| 2 | + |
| 3 | + |
| 4 | +import android.app.Service |
| 5 | +import android.content.Context |
| 6 | +import androidx.camera.core.CameraSelector |
| 7 | +import androidx.camera.core.ExperimentalGetImage |
| 8 | +import androidx.camera.core.ImageAnalysis |
| 9 | +import androidx.camera.core.ImageProxy |
| 10 | +import androidx.camera.lifecycle.ProcessCameraProvider |
| 11 | +import com.google.mlkit.vision.barcode.BarcodeScannerOptions |
| 12 | +import com.google.mlkit.vision.barcode.BarcodeScanning |
| 13 | +import com.google.mlkit.vision.barcode.common.Barcode |
| 14 | +import com.google.mlkit.vision.common.InputImage |
| 15 | +import androidx.core.content.ContextCompat |
| 16 | +import android.content.Intent |
| 17 | +import android.os.IBinder |
| 18 | +import android.os.Vibrator |
| 19 | +import android.util.Log |
| 20 | +import androidx.lifecycle.ProcessLifecycleOwner |
| 21 | +import org.jsoup.Jsoup |
| 22 | +import org.jsoup.nodes.Document |
| 23 | + |
| 24 | +/** |
| 25 | + * Classes and functions managing a background Service, + |
| 26 | + * which is constantly checking for barcodes |
| 27 | + * |
| 28 | + * TODO: Add voice command allowing to stop TTS |
| 29 | + */ |
| 30 | + |
| 31 | +/** |
| 32 | + * The Scanner is used to create a BarcodeScanner object, |
| 33 | + * analyze the imageProxy and check for barcodes, |
| 34 | + * and defines how to handle them. |
| 35 | + * |
| 36 | + * @param viewModel: viewModel allows the use of the say function |
| 37 | + */ |
| 38 | +class Scanner( |
| 39 | + private val viewModel: MainViewModel, |
| 40 | + private val vibrator: Vibrator |
| 41 | +) : ImageAnalysis.Analyzer { |
| 42 | + |
| 43 | + /** |
| 44 | + * BarcodeScanner object: Currently only scans EAN 13 barcodes |
| 45 | + */ |
| 46 | + private val options = BarcodeScannerOptions.Builder() |
| 47 | + .setBarcodeFormats( |
| 48 | + Barcode.FORMAT_EAN_13 |
| 49 | + ) |
| 50 | + .build() |
| 51 | + private val barcodeScanner = BarcodeScanning.getClient(options) |
| 52 | + |
| 53 | + /** |
| 54 | + * Analysis of the ImageProxy. Converts the camera output into analyzable format. |
| 55 | + * Applies the function handleBarcodeResult to the first barcode scanned. |
| 56 | + * This is embedded into a Thread to avoid to much calculations on the Main thread. |
| 57 | + * |
| 58 | + * @param image: uses the camera image without passing an argument directly |
| 59 | + * |
| 60 | + */ |
| 61 | + @ExperimentalGetImage |
| 62 | + override fun analyze(image: ImageProxy) { |
| 63 | + val mediaImage = image.image |
| 64 | + if (mediaImage != null) { |
| 65 | + val scannerInput = |
| 66 | + InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees) |
| 67 | + barcodeScanner.process(scannerInput) |
| 68 | + .addOnSuccessListener { barcodes -> |
| 69 | + for (barcode in barcodes) { |
| 70 | + Log.println(Log.INFO, "Scanner", "Barcode " + barcode.rawValue + " was detected") |
| 71 | + val urlAddOn = barcode.rawValue ?: "default" |
| 72 | + Thread { |
| 73 | + handleBarcodeResult(urlAddOn) |
| 74 | + }.start() |
| 75 | + break |
| 76 | + } |
| 77 | + } |
| 78 | + .addOnFailureListener { exception -> |
| 79 | + exception.printStackTrace() |
| 80 | + } |
| 81 | + .addOnCompleteListener { image.close() } |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + /** |
| 86 | + * Function to handle the Barcode Result. |
| 87 | + * Initiates a vibration to give feedback to the user, that a barcode was scanned |
| 88 | + * It creates an object BrowserSearch to use its function searchUrl |
| 89 | + * |
| 90 | + * @param urlAddOn: the Barcode as a String to use for web-search |
| 91 | + */ |
| 92 | + private fun handleBarcodeResult(urlAddOn: String) { |
| 93 | + vibrator.vibrate(200) |
| 94 | + val eanSearch = BrowserSearch() |
| 95 | + Log.println(Log.INFO, "Scraping", "Waiting for Scraping result") |
| 96 | + eanSearch.searchUrl(viewModel, urlAddOn) |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +/** |
| 101 | + * The BrowserSearch class embeds the web-search. |
| 102 | + * |
| 103 | + */ |
| 104 | +class BrowserSearch{ |
| 105 | + |
| 106 | + /** |
| 107 | + * The function to do the web-search and scraping. |
| 108 | + * Url: https://de.openfoodfacts.org/produkt/$barcode |
| 109 | + * searches the website with regards to the Barcodes value |
| 110 | + * Then scrapes the document for the specified elements and TTS the extracted text. |
| 111 | + * |
| 112 | + * @param viewModel: viewModel allows the use of the say function |
| 113 | + * @param barcode: the barcode as String to add on to the Url |
| 114 | + */ |
| 115 | + fun searchUrl (viewModel:MainViewModel, barcode: String) { |
| 116 | + try { |
| 117 | + val searchUrl = "https://de.openfoodfacts.org/produkt/$barcode" |
| 118 | + val document: Document = Jsoup.connect(searchUrl).get() |
| 119 | + val name = document.select("h1.title-3").first()?.text() |
| 120 | + val ingredients = |
| 121 | + document.getElementById("panel_ingredients_content")?.text() |
| 122 | + val allInfo = name.toString() + ingredients.toString() |
| 123 | + viewModel.say(allInfo) |
| 124 | + } catch (e: Exception) { |
| 125 | + e.printStackTrace() |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | +/** |
| 130 | + * The Scanning process handles the camera and initiates the imageAnalysis. |
| 131 | + * |
| 132 | + */ |
| 133 | + |
| 134 | +class ScanningProcess{ |
| 135 | + |
| 136 | + /** |
| 137 | + * Function starting the scanning process. |
| 138 | + * Selects the camera to be used, initializes an ImageAnalysis object, |
| 139 | + * from which to use the scanner method. |
| 140 | + * Also binds the camera to a Lifecycle |
| 141 | + * |
| 142 | + * @param viewModel: passed on to allow using the say function |
| 143 | + * @param context: context for the cameraProvider and the imageAnalyzes |
| 144 | + * |
| 145 | + * TODO: Change selector to other camera |
| 146 | + */ |
| 147 | + fun activateScanning(viewModel: MainViewModel, vibrator: Vibrator, context: Context) { |
| 148 | + |
| 149 | + val selector = CameraSelector.Builder() |
| 150 | + .requireLensFacing(CameraSelector.LENS_FACING_BACK) |
| 151 | + .build() |
| 152 | + val imageAnalysis = ImageAnalysis.Builder() |
| 153 | + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) |
| 154 | + .build() |
| 155 | + imageAnalysis.setAnalyzer( |
| 156 | + ContextCompat.getMainExecutor(context), |
| 157 | + Scanner(viewModel, vibrator) |
| 158 | + ) |
| 159 | + val cameraProvider = ProcessCameraProvider.getInstance(context).get() |
| 160 | + try { |
| 161 | + cameraProvider.bindToLifecycle( |
| 162 | + ProcessLifecycleOwner.get(), |
| 163 | + selector, |
| 164 | + imageAnalysis |
| 165 | + ) |
| 166 | + Log.println(Log.INFO, "Camera", "Camera binding successful") |
| 167 | + |
| 168 | + } catch (e: Exception) { |
| 169 | + e.printStackTrace().toString() |
| 170 | + } |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +/** |
| 175 | + * ConstantScanning is a Service, |
| 176 | + * meaning it is allowed to run in the background parallel to the other activity. |
| 177 | + * Also it runs without an UI, which is wanted in the case of the Scanner |
| 178 | + */ |
| 179 | + |
| 180 | +class ConstantScanning: Service() { |
| 181 | + |
| 182 | + /** |
| 183 | + * This method is required by the Service architecture, |
| 184 | + * but not needed because the scanning should be done constantly. |
| 185 | + * Therefore it just return null |
| 186 | + */ |
| 187 | + override fun onBind(intent: Intent?): IBinder? { |
| 188 | + return null |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Function that manages the Service, once initialized. |
| 193 | + * It creates a viewModel object to be passed on, so the say method can be used later on. |
| 194 | + * It also creates a Vibrator and Scanner object and starts the Scanning process. |
| 195 | + * Returns Start-Sticky to ensure it will try to start again, |
| 196 | + * if it is destroyed for whatever reason |
| 197 | + * |
| 198 | + * @param intent |
| 199 | + * @param flags |
| 200 | + * @param startId |
| 201 | + */ |
| 202 | + |
| 203 | + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
| 204 | + |
| 205 | + |
| 206 | + |
| 207 | + val application = application |
| 208 | + val viewModel = MainViewModel(application) |
| 209 | + viewModel.initTTS() |
| 210 | + val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator |
| 211 | + val scanner = ScanningProcess() |
| 212 | + scanner.activateScanning(viewModel, vibrator, this) |
| 213 | + Log.println(Log.INFO, "Scanner","Barcode Scanning Service is Active") |
| 214 | + |
| 215 | + return START_STICKY |
| 216 | + } |
| 217 | + |
| 218 | +} |
0 commit comments