Skip to content

Commit 9888b33

Browse files
committed
Your commit message here
1 parent aaa6ea5 commit 9888b33

6 files changed

Lines changed: 270 additions & 8 deletions

File tree

app/build.gradle

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ plugins {
88

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

1313
defaultConfig {
1414
applicationId "com.nlinterface"
1515
minSdk 24
16-
targetSdk 33
16+
//noinspection EditedTargetSdkVersion
17+
targetSdk 34
1718
versionCode 1
1819
versionName '0.0.1'
1920

@@ -36,16 +37,24 @@ android {
3637
buildFeatures {
3738
viewBinding true
3839
}
40+
packaging {
41+
resources {
42+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
43+
excludes += "META-INF/DEPENDENCIES"
44+
excludes += "mozilla/public-suffix-list.txt"
45+
}
46+
}
3947
}
4048

4149
dependencies {
4250

43-
implementation 'androidx.core:core-ktx:1.7.0'
51+
implementation 'androidx.core:core-ktx:1.12.0'
4452
implementation 'androidx.appcompat:appcompat:1.6.1'
4553
implementation 'com.google.android.material:material:1.5.0-alpha04'
4654
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
4755
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
4856
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
57+
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
4958
testImplementation 'junit:junit:4.13.2'
5059
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
5160
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@@ -61,4 +70,23 @@ dependencies {
6170

6271
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
6372

73+
//mlkit
74+
implementation 'com.google.mlkit:barcode-scanning:17.2.0'
75+
76+
//it.jsoup
77+
implementation "org.jsoup:jsoup:1.14.3"
78+
79+
80+
//androix.compose
81+
implementation(platform('androidx.compose:compose-bom:2023.08.00'))
82+
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
83+
debugImplementation 'androidx.compose.ui:ui-tooling'
84+
85+
86+
//camera
87+
implementation ("androidx.camera:camera-camera2:1.3.1")
88+
implementation("androidx.camera:camera-core:1.3.1")
89+
implementation("androidx.camera:camera-lifecycle:1.3.1")
90+
implementation("androidx.camera:camera-view:1.3.1")
91+
6492
}

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
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+
1012
<application
1113
android:allowBackup="true"
1214
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -43,7 +45,9 @@
4345
android:name=".activities.PlaceDetailsActivity" />
4446
<activity
4547
android:name=".activities.SettingsActivity"/>
46-
48+
<service
49+
android:name=".viewmodels.ConstantScanning"
50+
android:exported="false" />
4751
</application>
4852

4953
</manifest>

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

Lines changed: 22 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,10 @@ class MainActivity : AppCompatActivity() {
6465
configureUI()
6566
configureTTS()
6667
configureSTT()
68+
69+
verifyCameraPermissions()
70+
val serviceIntent = Intent(this, ConstantScanning()::class.java)
71+
startService(serviceIntent)
6772
}
6873

6974
/**
@@ -285,5 +290,21 @@ class MainActivity : AppCompatActivity() {
285290
viewModel.cancelListening()
286291
}
287292
}
288-
293+
294+
/**
295+
* Request the user to grant camera permissions, if not already granted.
296+
* Will probably not be used further once other camera is included
297+
*/
298+
private fun verifyCameraPermissions() {
299+
if (checkCallingOrSelfPermission(
300+
Manifest.permission.CAMERA
301+
) != PackageManager.PERMISSION_GRANTED
302+
) {
303+
ActivityCompat.requestPermissions(
304+
this,
305+
arrayOf(Manifest.permission.CAMERA),
306+
STT_PERMISSION_REQUEST_CODE
307+
)
308+
}
309+
}
289310
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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 androidx.lifecycle.ProcessLifecycleOwner
19+
import org.jsoup.Jsoup
20+
import org.jsoup.nodes.Document
21+
22+
/**
23+
* Classes and functions managing a background Service, +
24+
* which is constantly checking for barcodes
25+
*
26+
* TODO: Add voice command allowing to stop TTS
27+
*/
28+
29+
/**
30+
* The Scanner is used to create a BarcodeScanner object,
31+
* analyze the imageProxy and check for barcodes,
32+
* and defines how to handle them.
33+
*
34+
* @param viewModel: viewModel allows the use of the say function
35+
*/
36+
class Scanner(
37+
private val viewModel: MainViewModel
38+
) : ImageAnalysis.Analyzer {
39+
40+
/**
41+
* BarcodeScanner object: Currently only scans EAN 13 barcodes
42+
*/
43+
private val options = BarcodeScannerOptions.Builder()
44+
.setBarcodeFormats(
45+
Barcode.FORMAT_EAN_13
46+
)
47+
.build()
48+
private val barcodeScanner = BarcodeScanning.getClient(options)
49+
50+
/**
51+
* Analysis of the ImageProxy. Converts the camera output into analyzable format.
52+
* Applies the function handleBarcodeResult to the first barcode scanned.
53+
* This is embedded into a Thread to avoid to much calculations on the Main thread.
54+
*
55+
* @param image: uses the camera image without passing an argument directly
56+
*
57+
* TODO: potentially add sound feedback on scanned barcode,
58+
* TODO: hence the scraping and verbal feedback is a bit delayed
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+
val urlAddOn = barcode.rawValue ?: "default"
71+
Thread {
72+
handleBarcodeResult(urlAddOn)
73+
}.start()
74+
break
75+
}
76+
}
77+
.addOnFailureListener { exception ->
78+
exception.printStackTrace()
79+
}
80+
.addOnCompleteListener { image.close() }
81+
}
82+
}
83+
84+
/**
85+
* Function to handle the Barcode Result.
86+
* It creates an object BrowserSearch to use its function searchUrl
87+
*
88+
* @param urlAddOn: the Barcode as a String to use for web-search
89+
*/
90+
private fun handleBarcodeResult(urlAddOn: String) {
91+
// Handle the barcode result logic here
92+
val eanSearch = BrowserSearch()
93+
eanSearch.searchUrl(viewModel, urlAddOn)
94+
95+
}
96+
}
97+
98+
/**
99+
* The BrowserSearch class embeds the web-search.
100+
*
101+
*/
102+
class BrowserSearch{
103+
104+
/**
105+
* The function to do the web-search and scraping.
106+
* Url: https://de.openfoodfacts.org/produkt/$barcode
107+
* searches the website with regards to the Barcodes value
108+
* Then scrapes the document for the specified elements and TTS the extracted text.
109+
*
110+
* @param viewModel: viewModel allows the use of the say function
111+
* @param barcode: the barcode as String to add on to the Url
112+
*/
113+
fun searchUrl (viewModel:MainViewModel, barcode: String) {
114+
try {
115+
val searchUrl = "https://de.openfoodfacts.org/produkt/$barcode"
116+
val document: Document = Jsoup.connect(searchUrl).get()
117+
val name = document.select("h1.title-3").first()?.text()
118+
val ingredients =
119+
document.getElementById("panel_ingredients_content")?.text()
120+
val allInfo = name.toString() + ingredients.toString()
121+
viewModel.say(allInfo)
122+
} catch (e: Exception) {
123+
e.printStackTrace()
124+
}
125+
}
126+
}
127+
/**
128+
* The Scanning process handles the camera and initiates the imageAnalysis.
129+
*
130+
*/
131+
132+
class ScanningProcess{
133+
134+
/**
135+
* Function starting the scanning process.
136+
* Selects the camera to be used, initializes an ImageAnalysis object,
137+
* from which to use the scanner method.
138+
* Also binds the camera to a Lifecycle
139+
*
140+
* @param viewModel: passed on to allow using the say function
141+
* @param context: context for the cameraProvider and the imageAnalyzes
142+
*
143+
* TODO: Change selector to other camera
144+
*/
145+
fun activateScanning(viewModel: MainViewModel, context: Context) {
146+
147+
val selector = CameraSelector.Builder()
148+
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
149+
.build()
150+
val imageAnalysis = ImageAnalysis.Builder()
151+
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
152+
.build()
153+
imageAnalysis.setAnalyzer(
154+
ContextCompat.getMainExecutor(context),
155+
Scanner(viewModel)
156+
)
157+
val cameraProvider = ProcessCameraProvider.getInstance(context).get()
158+
try {
159+
cameraProvider.bindToLifecycle(
160+
ProcessLifecycleOwner.get(),
161+
selector,
162+
imageAnalysis
163+
)
164+
} catch (e: Exception) {
165+
e.printStackTrace()
166+
}
167+
}
168+
}
169+
170+
/**
171+
* ConstantScanning is a Service,
172+
* meaning it is allowed to run in the background parallel to the other activity.
173+
* Also it runs without an UI, which is wanted in the case of the Scanner
174+
*/
175+
176+
class ConstantScanning: Service() {
177+
178+
/**
179+
* This method is required by the Service architecture,
180+
* but not needed because the scanning should be done constantly.
181+
* Therefore it just return null
182+
*/
183+
override fun onBind(intent: Intent?): IBinder? {
184+
return null
185+
}
186+
187+
/**
188+
* Function that manages the Service, once initialized.
189+
* It creates a viewModel object to be passed on, so the say method can be used later on.
190+
* It also creates a Scanner object and starts the Scanning process.
191+
* Returns Start-Sticky to ensure it will try to start again,
192+
* if it is destroyed for whatever reason
193+
*
194+
* @param intent
195+
* @param flags
196+
* @param startId
197+
*/
198+
199+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
200+
val application = application
201+
val viewModel = MainViewModel(application)
202+
viewModel.initTTS()
203+
val scanner = ScanningProcess()
204+
scanner.activateScanning(viewModel, this)
205+
206+
return START_STICKY
207+
}
208+
209+
}

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)