Skip to content

Commit 37895b8

Browse files
author
Maximilian Häming
authored
Merge pull request #9 from StudyProject-NLI/Bene
Barcode Scanner MVP
2 parents aaa6ea5 + c2b6950 commit 37895b8

7 files changed

Lines changed: 275 additions & 9 deletions

File tree

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# Gradle files
2-
.gradle/
3-
build/
42

53
# Local configuration file (sdk path, etc)
64
local.properties

app/build.gradle

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ plugins {
88

99
android {
1010
namespace 'com.nlinterface'
11-
compileSdk 33
11+
compileSdk 34
1212

1313
defaultConfig {
1414
applicationId "com.nlinterface"
@@ -36,16 +36,24 @@ android {
3636
buildFeatures {
3737
viewBinding true
3838
}
39+
packaging {
40+
resources {
41+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
42+
excludes += "META-INF/DEPENDENCIES"
43+
excludes += "mozilla/public-suffix-list.txt"
44+
}
45+
}
3946
}
4047

4148
dependencies {
4249

43-
implementation 'androidx.core:core-ktx:1.7.0'
50+
implementation 'androidx.core:core-ktx:1.12.0'
4451
implementation 'androidx.appcompat:appcompat:1.6.1'
4552
implementation 'com.google.android.material:material:1.5.0-alpha04'
4653
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
4754
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
4855
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
56+
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
4957
testImplementation 'junit:junit:4.13.2'
5058
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
5159
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@@ -61,4 +69,16 @@ dependencies {
6169

6270
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
6371

72+
//mlkit
73+
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
74+
75+
//it.jsoup
76+
implementation "org.jsoup:jsoup:1.14.3"
77+
78+
//camera
79+
implementation ("androidx.camera:camera-camera2:1.3.1")
80+
implementation("androidx.camera:camera-core:1.3.1")
81+
implementation("androidx.camera:camera-lifecycle:1.3.1")
82+
implementation("androidx.camera:camera-view:1.3.1")
83+
6484
}

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
88
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
99

10+
<uses-permission-sdk-23 android:name="android.permission.CAMERA" />
11+
<uses-permission android:name="android.permission.VIBRATE" />
12+
1013
<application
1114
android:allowBackup="true"
1215
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -43,7 +46,9 @@
4346
android:name=".activities.PlaceDetailsActivity" />
4447
<activity
4548
android:name=".activities.SettingsActivity"/>
46-
49+
<service
50+
android:name=".viewmodels.ConstantScanning"
51+
android:exported="false" />
4752
</application>
4853

4954
</manifest>

app/src/main/java/com/nlinterface/activities/MainActivity.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.lifecycle.ViewModelProvider
1717
import com.nlinterface.R
1818
import com.nlinterface.databinding.ActivityMainBinding
1919
import com.nlinterface.utility.*
20+
import com.nlinterface.viewmodels.ConstantScanning
2021
import com.nlinterface.viewmodels.MainViewModel
2122

2223
/**
@@ -64,6 +65,14 @@ class MainActivity : AppCompatActivity() {
6465
configureUI()
6566
configureTTS()
6667
configureSTT()
68+
69+
verifyCameraPermissions()
70+
if (checkCallingOrSelfPermission(
71+
Manifest.permission.CAMERA
72+
) == PackageManager.PERMISSION_GRANTED) {
73+
val serviceIntent = Intent(this, ConstantScanning()::class.java)
74+
startService(serviceIntent)
75+
}
6776
}
6877

6978
/**
@@ -285,5 +294,21 @@ class MainActivity : AppCompatActivity() {
285294
viewModel.cancelListening()
286295
}
287296
}
288-
297+
298+
/**
299+
* Request the user to grant camera permissions, if not already granted.
300+
* Will probably not be used further once other camera is included
301+
*/
302+
private fun verifyCameraPermissions() {
303+
if (checkCallingOrSelfPermission(
304+
Manifest.permission.CAMERA
305+
) != PackageManager.PERMISSION_GRANTED
306+
) {
307+
ActivityCompat.requestPermissions(
308+
this,
309+
arrayOf(Manifest.permission.CAMERA),
310+
STT_PERMISSION_REQUEST_CODE
311+
)
312+
}
313+
}
289314
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
}

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ buildscript {
66
}
77

88
plugins {
9-
id 'com.android.application' version '8.1.2' apply false
10-
id 'com.android.library' version '8.1.2' apply false
9+
id 'com.android.application' version '8.2.0' apply false
10+
id 'com.android.library' version '8.2.0' apply false
1111
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
1212
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Thu May 25 17:04:02 CEST 2023
22
distributionBase=GRADLE_USER_HOME
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
44
distributionPath=wrapper/dists
55
zipStorePath=wrapper/dists
66
zipStoreBase=GRADLE_USER_HOME

0 commit comments

Comments
 (0)