Compare commits

...
53 Commits
Author SHA1 Message Date
Markus Fisch 47d5923a04 Advance version number to 1.14.0 2019-09-08 21:19:52 +02:00
Markus Fisch 8c22a8ab00 Apply auto formatting 2019-09-08 21:15:12 +02:00
molikunerandMarkus Fisch 8152cb6a24 Add OTP support 2019-09-08 21:06:05 +02:00
zmniandMarkus Fisch ffed4fc5cf Update Indonesian translation 2019-09-04 16:47:56 +02:00
Markus Fisch d8bd3c1176 Advance version number to 1.13.1 2019-08-31 10:45:02 +02:00
molikunerandMarkus Fisch 3b12aa4b7b Fix crash caused by missing \R support for Regex (#62)
* Fix crash caused by missing `\R` support for Regex
* Replaced `\R` usage in VType actions with official equivalent
* Replace `\R`, which was overlooked
2019-08-31 10:38:08 +02:00
Markus Fisch 099b522d35 Advance version number to 1.13.0 2019-08-23 18:32:03 +02:00
Markus Fisch 166d181469 Optimize imports according to Android Studio
Order of imports doesn't matter so I should simply let
Android Studio have its way.
2019-08-23 17:57:17 +02:00
Markus Fisch a4a2a67177 Add missing extras for ZXing result intents
Turns out, some apps do actually use them.
2019-08-23 17:55:39 +02:00
Markus Fisch 7fe3de78af Add screenshots to README 2019-08-09 14:05:28 +02:00
Markus Fisch e75d658092 Advance version number to 1.12.0 2019-08-05 20:29:16 +02:00
Markus Fisch c840462fa2 Fix folder name for Brazilian Portuguese
According to lint, it must read `values-pt-rBR`.
2019-08-05 20:25:51 +02:00
Ivan AmaroandMarkus Fisch c3475a37d2 Add Brazilian Portuguese translation 2019-08-05 14:03:19 +02:00
yzqzssandMarkus Fisch 183fe2c6f2 Add Simplified Chinese 2019-08-04 23:58:00 +02:00
ThomasandMarkus Fisch 056f5e263e Add Chinese translation
This set of Chinese translation is good for zh, zh-Hant, zh-TW, zh-HK and other zh variants. Some English nouns are kept as they are well known in common conversations.

Many app handle Chinese localization pickings differently (with a ton of political dicisions), but in general label this as "中文"/"Chinese" or "正體中文"/“traditional Chinese”.
2019-08-04 23:57:08 +02:00
Markus Fisch 386c7a6056 Encode text using UTF-8 encoding 2019-08-04 23:55:15 +02:00
Markus Fisch 80c876f35d Update description in README
With all the new features it's probably no longer fitting to
say that it doesn't do _anything_ else but decoding a barcode.
2019-07-31 13:19:07 +02:00
Markus Fisch 30ae51d945 Suppress lint warning about null root layout
Because dialogs don't have a parent layout and must
therefore be inflated with a null root layout.
2019-07-31 13:15:28 +02:00
Markus Fisch 95b54ed4ab Add some line breaks
Improves readability (for me).
2019-07-31 13:05:10 +02:00
Markus Fisch 3650bda107 Save preferences when leaving fragment only
Much simpler.
2019-07-30 13:48:19 +02:00
Markus Fisch 2bdf13761e Add preferences and the option to use a custom URL
For opening unknown barcodes. Should close #48.
2019-07-28 13:59:42 +02:00
Markus Fisch ee4ee1f778 Advance version number to 1.11.2 2019-07-24 22:30:27 +02:00
Markus Fisch dbb72747e9 Downgrade ZXing to version 3.3.3
Apparently 3.4.0 is crashing on some devices.
Further investigation required.

This reverts commit b888e37556.
2019-07-24 22:23:30 +02:00
Markus Fisch 8e577c0924 Use WindowInsets to get system bar metrics
Since reading system resources can go wrong and WindowInsets
are the way to go.

Watch this:
https://www.youtube.com/watch?v=_mGDMVRO3iE
2019-07-24 15:12:55 +02:00
Markus Fisch 6ce160447c Advance version number to 1.11.1 2019-07-22 11:06:14 +02:00
Markus Fisch b94a47ccb4 Update CameraView library to catch exception
Catch Camera.autoFocus() exceptions since some devices
throw a RuntimeException when it should work.
2019-07-22 11:00:40 +02:00
Markus Fisch ec34b33ce7 Advance version number to 1.11.0 2019-07-18 20:52:12 +02:00
Markus Fisch 024b95c12e Reapply auto format 2019-07-18 09:57:02 +02:00
molikunerandMarkus Fisch cf4dc2aa4a Add VTypes (VCard and VEvent)
* Ignoring case on SMS and TEL actions
* Added VCard action
* Added VEvent action
* Added abstract VTypeParser
2019-07-18 09:51:01 +02:00
Markus Fisch 1919ce2935 Make generated barcodes zoom/pinch-able
I'm not sure if this has any additional value but sometimes
I like to inspect the generated codes a little closer.
2019-07-16 14:42:16 +02:00
Markus Fisch 1e3dfb64ae Fix building PDF417 barcode
Use the dimensions from the generated barcode for the
stride. ZXing adds quite some margin around a PDF417
and that breaks Bitmap.setPixels() when stride is set
to the target width.
2019-07-16 14:09:24 +02:00
Markus Fisch f87eaa82dd Advance version number to 1.10.0 2019-07-10 21:47:58 +02:00
Markus Fisch 13b90186d3 Include JUnit tests in Makefile test rule too 2019-07-10 16:21:30 +02:00
Markus Fisch 2c4e329663 Make tool and system bars completely transparent
Results in a lighter, more modern look.
2019-07-10 16:12:30 +02:00
Markus Fisch 624e129ef7 Remove duplicated code when executing share intent
And use the new function `execShareIntent()` in IAction too.
2019-07-10 16:07:19 +02:00
Markus Fisch 2f19bd7fc9 Add some line breaks to improve readability
And clean up imports which is a silly thing to do, of course.
I just can't help it.
2019-07-10 16:05:59 +02:00
Markus Fisch da48539585 Update build tools version 2019-07-10 16:02:09 +02:00
Balázs ÚrandMarkus Fisch a1d263837a Update Hungarian translation 2019-07-08 23:09:09 +02:00
molikunerandMarkus Fisch 188824ecaa Optimize code and regex
* Simplified code of WiFi parsing
* Optimized regex of Mail, SMS, and WiFi
* Fixed possible crash on Gingerbread or lower because of missing value
* Resolved unexpected behavior when no application was found to share sth
* Used more Kotlin way of getting ViewHolder variable of View tag
* Used more Kotlin way of getting the current content in DecodeFragment
2019-07-08 20:38:21 +02:00
Markus Fisch f5376290ab Convert text to bytes if byte segments are shorter
Because Zxing cuts off content prefixes like "WIFI:" from
byte segments what renders the bytes segments useless.

Since byte segments are important for binary content only,
it's probably to safe to ignore them for text content.
2019-07-05 21:10:29 +02:00
Markus Fisch b888e37556 Update ZXing to latest version 2019-07-05 21:05:00 +02:00
Markus Fisch cb43160902 Apply Kotlin Coding Conventions
Apply Android Studio's Reformat Code using Kotlin Coding Conventions,
see https://kotlinlang.org/docs/reference/coding-conventions.html
2019-07-05 15:27:15 +02:00
molikunerandMarkus Fisch a3ad6aa4c5 Add Mail, SMS and Telephone action
* Added `SimpleIntentIAction` to simplify code
* Change actions registry Array to Set
2019-07-05 15:21:37 +02:00
Markus Fisch d0f0bedae2 Restore tools:text for hex view 2019-07-05 12:58:09 +02:00
Markus Fisch 3f58349ce8 Fix indents in resources and new code
Replace spaces with tabs in string resources for new
resources.

Fix indenting according to Kotlin Coding Conventions.

Add a few line breaks and braces where it improves
readability for me.
2019-07-05 11:18:55 +02:00
molikunerandMarkus Fisch e90d7d5f0e Fix WiFi parsing and remove extra button
* Do some stuff in background to keep UI thread smooth
* Replaced WifiNetworkInfoFactory with WifiConfigurationFactory
* Added utility functions and titleResId to IAction
* Removed unused button from layout
2019-07-05 10:50:41 +02:00
Markus Fisch b39e5d2721 Replace space indents with tabs
To make the new code match to what's there.
We don't want to mix tabs with spaces.
2019-07-04 18:43:08 +02:00
Markus Fisch 3ede2713b6 Add translations for new WiFi button
Necessary or lint will fail.
2019-07-04 18:36:05 +02:00
Joe SteinandMarkus Fisch dc2fc292ce Add ability to connect to WiFi via button
* Added some tools:text for designing in Android Studio
* Add Connect to Wifi button
* Background for wifi button
* Added Action framework and WifiAction
* Unit tests for WifiAction
* Separate WiFi parsing and connecting code
* Remove network name escaping
* Make button slightly lighter when pressed
2019-07-04 18:31:43 +02:00
zmniandMarkus Fisch 3dc50d2d7a Add Indonesian translation 2019-07-01 20:38:13 +02:00
Balázs ÚrandMarkus Fisch 3d15425460 Update Hungarian translation 2019-06-28 10:36:28 +02:00
Markus Fisch 412e27ee81 Advance version number to 1.9.1 2019-06-20 14:20:00 +02:00
Markus Fisch d73ea341c0 Ask for search engine when searching for content
Instead of opening Google when searching for decoded content,
there's now a dialog to pick a suitable search engine from.

Especially useful if the user already knows what category
the scanned item belongs to.
2019-06-20 14:08:59 +02:00
65 changed files with 1950 additions and 239 deletions
+37
View File
@@ -1,5 +1,42 @@
# Change Log
# 1.14.0
* Add OTP support
* Update Indonesian translation
# 1.13.1
* Fix crash caused by missing support for Regex on Android < 6
# 1.13.0
* Add missing extras for ZXing result intents
# 1.12.0
* Add preferences dialog and the option to use a custom URL with read contents
* Add Brazilian Portuguese translation
* Add Chinese translation
* Encode text using UTF-8 encoding when creating a barcode
# 1.11.2
* Downgrade ZXing version
* Improve getting system UI metrics
# 1.11.1
* Fix tap to focus on some devices
# 1.11.0
* Add support for VCard and VEvent barcodes contents
* Generated barcodes are now zoomable
* Fix creating PDF417 barcodes
## 1.10.0
* Add support for special barcode contents (WiFi, SMS, phone, E-mail)
* Add Indonesian translation
* Update Hungarian translation
* Make system bars completely transparent
## 1.9.1
* Pick search engine for content that is not an URL
## 1.9.0
* Try Google when opening content as URL fails
* Show generic error message if barcode generation fails
+1 -1
View File
@@ -21,7 +21,7 @@ infer: clean
infer -- ./gradlew assembleDebug
test:
./gradlew cAT
./gradlew test cAT
install:
adb $(TARGET) install -r app/build/outputs/apk/debug/app-debug.apk
+18 -4
View File
@@ -5,15 +5,29 @@ Yet another barcode scanner for Android. As if there weren't [enough][play].
This one is totally free, open source and does use the [ZXing][zxing] barcode
scanning library.
Unlike many others it works in portrait and landscape orientation, can read
inverted codes, uses Material Design and doesn't do anything else but decoding
Unlike many others, it works in portrait and landscape orientation, can read
inverted codes, comes in Material Design and doesn't do much else but decoding
a barcode.
<a href="https://f-droid.org/en/packages/de.markusfisch.android.binaryeye/"><img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="80"/></a> <a href="https://play.google.com/store/apps/details?id=de.markusfisch.android.binaryeye"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" height="80"/></a>
And it's written in [Kotlin][kotlin] because I wanted to explore that a
bit more.
## Screenshots
<img
src="https://raw.githubusercontent.com/markusfisch/BinaryEye/gh-pages/screencap-scanning.png"
alt="Screenshot Scanning" width="160"/>
<img
src="https://raw.githubusercontent.com/markusfisch/BinaryEye/gh-pages/screencap-decoded.png"
alt="Screenshot Result" width="160"/>
<img
src="https://raw.githubusercontent.com/markusfisch/BinaryEye/gh-pages/screencap-compose-barcode.png"
alt="Screenshot Compose Barcode" width="160"/>
## Download
<a href="https://f-droid.org/en/packages/de.markusfisch.android.binaryeye/"><img alt="Get it on F-Droid" src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="80"/></a> <a href="https://play.google.com/store/apps/details?id=de.markusfisch.android.binaryeye"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" height="80"/></a>
[play]: https://play.google.com/store/search?q=barcode%20scanner&c=apps
[zxing]: https://github.com/zxing/zxing
[kotlin]: http://kotlinlang.org/
+5 -3
View File
@@ -9,8 +9,8 @@ android {
minSdkVersion 9
targetSdkVersion sdk_version
versionCode 28
versionName '1.9.0'
versionCode 39
versionName '1.14.0'
// it's recommended to set this value to the lowest API level
// able to provide all the functionality
@@ -33,6 +33,7 @@ android {
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
androidTest.java.srcDirs += 'src/androidTest/kotlin'
test.java.srcDirs += 'src/test/kotlin'
}
buildTypes {
@@ -70,5 +71,6 @@ dependencies {
implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:design:$support_version"
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.github.markusfisch:CameraView:1.8.3'
implementation 'com.github.markusfisch:CameraView:1.8.4'
implementation 'com.github.markusfisch:ScalingImageView:1.1.3'
}
+2
View File
@@ -8,6 +8,8 @@
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<application
@@ -0,0 +1,19 @@
package de.markusfisch.android.binaryeye.actions
import de.markusfisch.android.binaryeye.actions.mail.MailAction
import de.markusfisch.android.binaryeye.actions.otpauth.OtpauthAction
import de.markusfisch.android.binaryeye.actions.sms.SmsAction
import de.markusfisch.android.binaryeye.actions.tel.TelAction
import de.markusfisch.android.binaryeye.actions.vtype.vcard.VCardAction
import de.markusfisch.android.binaryeye.actions.vtype.vevent.VEventAction
import de.markusfisch.android.binaryeye.actions.wifi.WifiAction
object ActionRegistry {
val REGISTRY: Set<IAction> = setOf(
MailAction, OtpauthAction, SmsAction, TelAction, VCardAction, VEventAction, WifiAction
)
fun getAction(data: ByteArray): IAction? = REGISTRY.find {
it.canExecuteOn(data)
}
}
@@ -0,0 +1,36 @@
package de.markusfisch.android.binaryeye.actions
import android.content.Context
import android.content.Intent
import android.widget.Toast
import de.markusfisch.android.binaryeye.app.execShareIntent
interface IAction {
val iconResId: Int
val titleResId: Int
fun canExecuteOn(data: ByteArray): Boolean
fun execute(context: Context, data: ByteArray)
}
abstract class SimpleIntentIAction : IAction {
abstract val errorMsg: Int
final override fun execute(context: Context, data: ByteArray) {
val intent =
executeForIntent(context, data) ?: return Toast.makeText(
context,
errorMsg,
Toast.LENGTH_LONG
).show()
execShareIntent(context, intent)
}
abstract fun executeForIntent(context: Context, data: ByteArray): Intent?
}
fun IAction?.validateOrGetNew(data: ByteArray): IAction? {
return this?.takeIf {
canExecuteOn(data)
} ?: ActionRegistry.getAction(data)
}
@@ -0,0 +1,30 @@
package de.markusfisch.android.binaryeye.actions.mail
import android.content.Context
import android.content.Intent
import android.net.Uri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
object MailAction : SimpleIntentIAction() {
private val mailRegex =
"""^mail(?:to)?:([\w.%+-]+@[A-Za-z\d.-]+\.[A-Za-z]{2,6}(?:[?&](?:subject|body)=[\S\s]*?){0,2})$""".toRegex()
override val iconResId: Int = R.drawable.ic_action_mail
override val titleResId: Int = R.string.mail_send
override val errorMsg: Int = R.string.mail_error
override fun canExecuteOn(data: ByteArray): Boolean {
return String(data).matches(mailRegex)
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
val mailWithMessage = mailRegex.matchEntire(
String(data)
)?.groupValues?.get(1) ?: return null
return Intent(
Intent.ACTION_SENDTO,
Uri.parse("mailto:$mailWithMessage")
)
}
}
@@ -0,0 +1,22 @@
package de.markusfisch.android.binaryeye.actions.otpauth
import android.content.Context
import android.content.Intent
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
object OtpauthAction : SimpleIntentIAction() {
override val iconResId: Int = R.drawable.ic_action_otpauth
override val titleResId: Int = R.string.otpauth_add
override val errorMsg: Int = R.string.otpauth_error
override fun canExecuteOn(data: ByteArray): Boolean {
return OtpauthParser(String(data)) != null
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
return OtpauthParser(String(data))?.let {
Intent(Intent.ACTION_VIEW, it.uri)
}
}
}
@@ -0,0 +1,76 @@
package de.markusfisch.android.binaryeye.actions.otpauth
import android.net.Uri
class OtpauthParser private constructor(private val match: MatchResult) {
val otpType: String
get() = match.groupValues[1]
private val labelIssuer: String
get() = match.groupValues[2]
val accountName: String
get() = match.groupValues[3].takeIf { it.isNotEmpty() } ?: match.groupValues[4]
val secret: String
get() = match.groupValues[5]
private val parameterIssuer: String
get() = match.groupValues[6]
val algorithm: String
get() = match.groupValues[7].takeIf { it.isNotEmpty() } ?: "SHA1"
val digits: Int
get() = match.groupValues[8].toIntOrNull() ?: 6
val period: Int
get() = match.groupValues[9].toIntOrNull() ?: 30
private val parameterCounter: String
get() = match.groupValues[10]
val issuer: String
get() = labelIssuer.takeIf { it.isNotEmpty() } ?: parameterIssuer
val counter: Int
get() = parameterCounter.toInt()
private val isHotp: Boolean = otpType == "hotp"
val isValid: Boolean =
(labelIssuer.isNotEmpty() == parameterIssuer.isEmpty() || labelIssuer.isEmpty())
&& secret.isNotEmpty() && isHotp == parameterCounter.isNotEmpty()
val uri: Uri
get() = Uri.Builder().apply {
scheme("otpauth")
authority(otpType)
if (issuer.isNotEmpty()) {
encodedPath("/$issuer:$accountName")
appendQueryParameter("issuer", issuer)
} else {
encodedPath("/$accountName")
}
appendQueryParameter("secret", secret)
appendQueryParameter("algorithm", algorithm)
appendQueryParameter("digits", digits.toString())
if (isHotp) {
appendQueryParameter("counter", counter.toString())
} else {
appendQueryParameter("period", period.toString())
}
}.build()
companion object {
private const val issuerAndAccount =
"""(?:(?:([^?:]+)(?::|%3A)(?:%20)*([^?]+))|([^?]+))"""//.toRegex()
/** allowing `padding` even though it should not be there, will be removed in output*/
private const val secret = """(?:secret=([2-7A-Z]+)=*)"""//.toRegex()
/** `\2` references the issuer of [issuerAndAccount] */
private const val issuer = """(?:issuer=\2)|(?:issuer=([^&]+))"""//.toRegex()
private const val algorithm = """(?:algorithm=(SHA(?:1|256|512)))"""//.toRegex()
private const val digits = """(?:digits=([0-9]+))"""//.toRegex()
private const val period = """(?:period=([0-9]+))"""//.toRegex()
private const val counter = """(?:counter=([0-9]+))"""//.toRegex()
private val otpauthRegex =
"""^otpauth://([ht]otp)/$issuerAndAccount\?(?:&?(?:$secret|$issuer|$algorithm|$digits|$period|$counter))+$""".toRegex(
RegexOption.IGNORE_CASE
)
operator fun invoke(input: String): OtpauthParser? {
return otpauthRegex.matchEntire(input)?.let(::OtpauthParser)?.takeIf { it.isValid }
}
}
}
@@ -0,0 +1,34 @@
package de.markusfisch.android.binaryeye.actions.sms
import android.content.Context
import android.content.Intent
import android.net.Uri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
object SmsAction : SimpleIntentIAction() {
private val smsRegex =
"""^sms(?:to)?:(\+?[0-9]+)(?::([\S\s]*))?$""".toRegex(RegexOption.IGNORE_CASE)
override val iconResId: Int = R.drawable.ic_action_sms
override val titleResId: Int = R.string.sms_send
override val errorMsg: Int = R.string.sms_error
override fun canExecuteOn(data: ByteArray): Boolean {
return String(data).matches(smsRegex)
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
val (number: String, message: String) = smsRegex.matchEntire(
String(data)
)?.let {
it.groupValues[1] to it.groupValues[2]
} ?: return null
return Intent(
Intent.ACTION_SENDTO,
Uri.parse("smsto:$number")
).apply {
if (message.isNotEmpty()) putExtra("sms_body", message)
}
}
}
@@ -0,0 +1,23 @@
package de.markusfisch.android.binaryeye.actions.tel
import android.content.Context
import android.content.Intent
import android.net.Uri
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
object TelAction : SimpleIntentIAction() {
private val telRegex = """^tel:(\+?[0-9]+)$""".toRegex(RegexOption.IGNORE_CASE)
override val iconResId: Int = R.drawable.ic_action_tel
override val titleResId: Int = R.string.tel_dial
override val errorMsg: Int = R.string.tel_error
override fun canExecuteOn(data: ByteArray): Boolean {
return String(data).matches(telRegex)
}
override fun executeForIntent(context: Context, data: ByteArray): Intent {
return Intent(Intent.ACTION_DIAL, Uri.parse(String(data)))
}
}
@@ -0,0 +1,34 @@
package de.markusfisch.android.binaryeye.actions.vtype
object VTypeParser {
private const val BACKSLASH_R_LEGACY =
"(?:\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])"
private val vTypeRegex =
"""^BEGIN:(VCARD|VEVENT)(?:$BACKSLASH_R_LEGACY.+?:[\s\S]+?)+?${BACKSLASH_R_LEGACY}END:\1$BACKSLASH_R_LEGACY?$""".toRegex(
RegexOption.IGNORE_CASE
)
private val propertyRegex = """^(.+?):([\s\S]*?)$""".toRegex(RegexOption.MULTILINE)
fun parseVType(data: String): String? =
vTypeRegex.matchEntire(data)?.groupValues?.get(1)?.toUpperCase()
fun parseMap(data: String): Map<String, List<VTypeProperty>> = propertyRegex.findAll(data).map {
it.groupValues[1].split(';').let { typeAndInfo ->
typeAndInfo[0] to VTypeProperty(typeAndInfo.drop(1), it.groupValues[2])
}
}.groupBy({ it.first.toUpperCase() }) { it.second }
}
data class VTypeProperty(
val info: List<String>,
val value: String
) {
val firstTypeOrFirstInfo: String?
get() = this.info.find { it.startsWith("TYPE=", true) }?.let {
typeRegex.matchEntire(it)?.groupValues?.get(1)
} ?: this.info.firstOrNull()
companion object {
private val typeRegex = """^(?:TYPE=)(.+?)[:;]?.*$""".toRegex(RegexOption.IGNORE_CASE)
}
}
@@ -0,0 +1,178 @@
package de.markusfisch.android.binaryeye.actions.vtype.vcard
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.ContactsContract
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
object VCardAction : SimpleIntentIAction() {
override val iconResId: Int
get() = R.drawable.ic_action_vcard
override val titleResId: Int
get() = R.string.vcard_add
override val errorMsg: Int
get() = R.string.vcard_failed
override fun canExecuteOn(data: ByteArray): Boolean {
return VTypeParser.parseVType(String(data)) == "VCARD"
}
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
val info = VTypeParser.parseMap(String(data))
return Intent(Intent.ACTION_INSERT_OR_EDIT).apply {
type = ContactsContract.Contacts.CONTENT_ITEM_TYPE
info["FN"]?.singleOrNull()?.also { name ->
putExtra(ContactsContract.Intents.Insert.NAME, name.value)
} ?: info["N"]?.singleOrNull()?.also { name ->
putExtra(ContactsContract.Intents.Insert.NAME, name.value.nameFormat)
}
info["TEL"]?.filter { it.value.isNotEmpty() }?.forEachIndexed { index, phoneProperty ->
val (extraPhoneType, extraPhone) = when (index) {
0 -> ContactsContract.Intents.Insert.PHONE_TYPE to
ContactsContract.Intents.Insert.PHONE
1 -> ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE to
ContactsContract.Intents.Insert.SECONDARY_PHONE
2 -> ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE to
ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE
else -> return@forEachIndexed
}
phoneProperty.firstTypeOrFirstInfo?.also {
it.phoneType?.also { phoneType ->
putExtra(extraPhoneType, phoneType)
} ?: putExtra(extraPhoneType, it)
}
putExtra(extraPhone, phoneProperty.value.toLowerCase().removePrefix("tel:"))
}
info["EMAIL"]?.forEachIndexed { index, mailProperty ->
val (extraMailType, extraMail) = when (index) {
0 -> ContactsContract.Intents.Insert.EMAIL_TYPE to
ContactsContract.Intents.Insert.EMAIL
1 -> ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE to
ContactsContract.Intents.Insert.SECONDARY_EMAIL
2 -> ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE to
ContactsContract.Intents.Insert.TERTIARY_EMAIL
else -> return@forEachIndexed
}
mailProperty.firstTypeOrFirstInfo?.also {
it.mailType?.also { mailType ->
putExtra(extraMailType, mailType)
} ?: putExtra(extraMailType, it)
}
putExtra(extraMail, mailProperty.value)
}
info["ORG"]?.singleOrNull()?.also { organization ->
putExtra(ContactsContract.Intents.Insert.COMPANY, organization.value)
}
info["TITLE"]?.singleOrNull()?.also { jobTitle ->
putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobTitle.value)
}
info["ADR"]?.forEach { addr ->
addr.firstTypeOrFirstInfo?.also {
it.addressType?.also { addressType ->
putExtra(ContactsContract.Intents.Insert.POSTAL_TYPE, addressType)
} ?: putExtra(ContactsContract.Intents.Insert.POSTAL_TYPE, it)
}
putExtra(ContactsContract.Intents.Insert.POSTAL, addr.value.locationFormat)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
val urls = info["URL"]?.mapNotNull {
it.value.takeIf { url -> url.isNotBlank() }?.let { url ->
ContentValues(2).apply {
put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE
)
put(ContactsContract.CommonDataKinds.Website.URL, url)
}
}
}
val bDays = info["BDAY"]?.mapNotNull {
it.value.takeIf { day -> day.isNotBlank() }?.let { day ->
ContentValues(3).apply {
put(
ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE
)
put(
ContactsContract.CommonDataKinds.Event.TYPE,
ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY
)
put(ContactsContract.CommonDataKinds.Event.START_DATE, day)
}
}
}
putParcelableArrayListExtra(
ContactsContract.Intents.Insert.DATA,
ArrayList(listOfNotNull(urls, bDays).flatten())
)
}
}
}
private val String.locationFormat: String
get() = """^([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?)$""".toRegex().matchEntire(
this
)?.groupValues?.let {
listOf(
it[1],
it[2],
it[3],
"${it[4]} ${it[6]}",
"${it[5]} ${it[7]}"
).filter { line -> line.isNotBlank() }
.joinToString("\n").takeIf { location -> location.isNotBlank() }
} ?: this
private val String.nameFormat: String
get() = """^([^;]*?);([^;]*?);([^;]*?);([^;]*?);([^;]*?)$""".toRegex().matchEntire(this)?.groupValues?.let {
listOf(it[4], it[2], it[3], it[1], it[5]).joinToString(" ")
.takeIf { name -> name.isNotBlank() }
} ?: this
private val String.mailType: Int?
get() = when (this.toUpperCase()) {
"HOME" -> ContactsContract.CommonDataKinds.Email.TYPE_HOME
"WORK" -> ContactsContract.CommonDataKinds.Email.TYPE_WORK
"OTHER" -> ContactsContract.CommonDataKinds.Email.TYPE_OTHER
"MOBILE" -> ContactsContract.CommonDataKinds.Email.TYPE_MOBILE
else -> null
}
private val String.phoneType: Int?
get() = when (this.toUpperCase()) {
"HOME" -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME
"MOBILE", "CELL" -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
"WORK" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK
"FAX_WORK" -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK
"FAX_HOME" -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME
"PAGER" -> ContactsContract.CommonDataKinds.Phone.TYPE_PAGER
"OTHER" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER
"CALLBACK" -> ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK
"CAR" -> ContactsContract.CommonDataKinds.Phone.TYPE_CAR
"COMPANY_MAIN" -> ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN
"ISDN" -> ContactsContract.CommonDataKinds.Phone.TYPE_ISDN
"MAIN" -> ContactsContract.CommonDataKinds.Phone.TYPE_MAIN
"OTHER_FAX" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX
"RADIO" -> ContactsContract.CommonDataKinds.Phone.TYPE_RADIO
"TELEX" -> ContactsContract.CommonDataKinds.Phone.TYPE_TELEX
"TTY_TDD" -> ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD
"WORK_MOBILE" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE
"WORK_PAGER" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER
"ASSISTANT" -> ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT
"MMS" -> ContactsContract.CommonDataKinds.Phone.TYPE_MMS
else -> null
}
private val String.addressType: Int?
get() = when (this.toUpperCase()) {
"HOME" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME
"WORK" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK
"OTHER" -> ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER
else -> null
}
}
@@ -0,0 +1,78 @@
package de.markusfisch.android.binaryeye.actions.vtype.vevent
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.CalendarContract
import android.support.annotation.RequiresApi
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
import java.text.SimpleDateFormat
import java.util.*
object VEventAction : SimpleIntentIAction() {
override val iconResId: Int
get() = R.drawable.ic_action_vevent
override val titleResId: Int
get() = R.string.vevent_add
override val errorMsg: Int
get() = R.string.vevent_failed
override fun canExecuteOn(data: ByteArray): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH
&& VTypeParser.parseVType(String(data)) == "VEVENT"
}
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
override fun executeForIntent(context: Context, data: ByteArray): Intent? {
val info = VTypeParser.parseMap(String(data))
return Intent(Intent.ACTION_EDIT).apply {
type = "vnd.android.cursor.item/event"
info["SUMMARY"]?.singleOrNull()?.also { title ->
putExtra(CalendarContract.Events.TITLE, title.value)
}
info["DESCRIPTION"]?.singleOrNull()?.also { description ->
putExtra(CalendarContract.Events.DESCRIPTION, description.value)
}
info["LOCATION"]?.singleOrNull()?.also { location ->
putExtra(CalendarContract.Events.EVENT_LOCATION, location.value)
}
info["DTSTART"]?.singleOrNull()?.also { eventStart ->
dateFormats.simpleFindParse(eventStart.value)?.also {
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, it.time)
}
}
info["DTEND"]?.singleOrNull()?.also { eventEnd ->
dateFormats.simpleFindParse(eventEnd.value)?.also {
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, it.time)
}
}
}
}
@SuppressLint("SimpleDateFormat") // we definitely don't wan't the local format
private val dateFormats = listOf(
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"),
SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"),
SimpleDateFormat("yyyyMMdd'T'HHmmss"),
SimpleDateFormat("yyyy-MM-dd"),
SimpleDateFormat("yyyyMMdd")
)
private fun List<SimpleDateFormat>.simpleFindParse(data: String): Date? {
for (simpleDataFormat in this) {
return simpleDataFormat.simpleParse(data) ?: continue
}
return null
}
private fun SimpleDateFormat.simpleParse(data: String): Date? = try {
this.parse(data)
} catch (e: Exception) {
null
}
}
@@ -0,0 +1,91 @@
package de.markusfisch.android.binaryeye.actions.wifi
import android.content.Context
import android.content.Context.WIFI_SERVICE
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiManager
import android.widget.Toast
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IAction
import kotlinx.coroutines.*
object WifiAction : IAction {
override val iconResId = R.drawable.ic_action_wifi
override val titleResId = R.string.connect_to_wifi
override fun canExecuteOn(data: ByteArray): Boolean =
WifiConfigurationFactory.parse(String(data)) != null
override fun execute(context: Context, data: ByteArray) {
CoroutineScope(Dispatchers.IO).launch {
val wifiConfig = WifiConfigurationFactory.parse(
String(data)
) ?: return@launch
val wifiManager = context.applicationContext.getSystemService(
WIFI_SERVICE
) as WifiManager
wifiManager.enableWifi(context)
wifiManager.mayRemoveOldNetwork(wifiConfig)
wifiManager.enableNewNetwork(wifiConfig)
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_added,
Toast.LENGTH_LONG
).show()
}
}
}
private suspend fun WifiManager.enableWifi(context: Context): Boolean {
if (!this.isWifiEnabled) {
if (!this.setWifiEnabled(true)) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_config_failed,
Toast.LENGTH_LONG
).show()
}
return false
}
var i = 0
while (!this.isWifiEnabled) {
if (i >= 10) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
R.string.wifi_config_failed,
Toast.LENGTH_LONG
).show()
}
return false
}
delay(1000)
i++
}
}
return true
}
private suspend fun WifiManager.mayRemoveOldNetwork(
wifiConfig: WifiConfiguration
) {
configuredNetworks?.firstOrNull {
it.SSID == wifiConfig.SSID &&
it.allowedKeyManagement == wifiConfig.allowedKeyManagement
}?.networkId?.also {
removeNetwork(it)
}
}
private suspend fun WifiManager.enableNewNetwork(
wifiConfig: WifiConfiguration
) {
val id = addNetwork(wifiConfig)
disconnect()
enableNetwork(id, true)
reconnect()
}
}
@@ -0,0 +1,211 @@
package de.markusfisch.android.binaryeye.actions.wifi
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiEnterpriseConfig
import android.os.Build
/**
* Normal:
* WIFI:S:[network SSID];T:<WPA|WEP|nopass|>;P:[network password];H:<true|false|>;;
*
* WPA2 enterprise (EAP):
* WIFI:S:[network SSID];T:WPA2-EAP;H:<true|false|nopass|>;E:[EAP method];PH2:[Phase 2 method];AI:[anonymous identity];I:[identity];P:[password];;
*
* EPA methods:
* "AKA", "AKA_PRIME",
* "NONE", "PEAP",
* "PWD", "SIM",
* "TLS", "TTLS",
* "UNAUTH_TLS"
* https://developer.android.com/reference/android/net/wifi/WifiEnterpriseConfig.Eap.html
*
* Phase 2 methods:
* "AKA", "AKA_PRIME",
* "GTC", "MSCHAP",
* "MSCHAPV2", "NONE",
* "PAP", "SIM"
* https://developer.android.com/reference/android/net/wifi/WifiEnterpriseConfig.Phase2.html
*
* The fields can appear in any order. Only "S:" is required.
*/
object WifiConfigurationFactory {
fun parse(input: String): WifiConfiguration? {
val inputMap = parseMap(input) ?: return null
val parsedData = SimpleDataAccessor.of(inputMap) ?: return null
return WifiConfiguration().apply(parsedData)
}
internal fun parseMap(string: String): Map<String, String>? {
// normally those codes should have the last semicolon, but many
// generators don't add it
val wifiRegex = """^WIFI:((?:.+?:(?:[^\\;]|\\.)*;)+);?$""".toRegex()
// should be: ^(.+):((?:[^\\;,":]|\\.)*);$ but allows unescaped , "
// and : because many qr creator doesn't escape
val pairRegex = """(.+?):((?:[^\\;]|\\.)*);""".toRegex()
return wifiRegex.matchEntire(
string
)?.groupValues?.get(1)?.let { pairs ->
pairRegex.findAll(pairs).map { pair ->
pair.groupValues[1].toUpperCase() to pair.groupValues[2]
}.toMap()
}
}
internal class SimpleDataAccessor private constructor(
private val inputMap: Map<String, String>
) {
private val hexRegex = """^[0-9a-f]+$""".toRegex(
RegexOption.IGNORE_CASE
)
// keep possibility of wrongly not escaped \ by explicitly searching
// for special chars
private val escapedRegex = """\\([\\;,":])""".toRegex()
// this should be private but because of testing it isn't possible
internal val ssid: String
get() = inputMap.getValue("S").quotedUnlessHex.unescaped
internal val securityType: String
get() = inputMap["T"]?.unescaped ?: ""
internal val password: String?
get() = inputMap["P"]?.quotedUnlessHex?.unescaped
internal val hidden: Boolean
get() = inputMap["H"]?.unescaped == "true"
internal val anonymousIdentity: String
get() = inputMap["AI"]?.unescaped ?: ""
internal val identity: String
get() = inputMap["I"]?.unescaped ?: ""
internal val eapMethod: Int?
get() = if (inputMap["E"].isNullOrEmpty()) {
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
WifiEnterpriseConfig.Eap.NONE
}
} else when (inputMap["E"]) {
"AKA" -> requireSdk(Build.VERSION_CODES.LOLLIPOP) { WifiEnterpriseConfig.Eap.AKA }
"AKA_PRIME" -> requireSdk(Build.VERSION_CODES.M) { WifiEnterpriseConfig.Eap.AKA_PRIME }
"NONE" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.NONE }
"PEAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.PEAP }
"PWD" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.PWD }
"SIM" -> requireSdk(Build.VERSION_CODES.LOLLIPOP) { WifiEnterpriseConfig.Eap.SIM }
"TLS" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.TLS }
"TTLS" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Eap.TTLS }
"UNAUTH_TLS" -> requireSdk(Build.VERSION_CODES.N) { WifiEnterpriseConfig.Eap.UNAUTH_TLS }
else -> null
}
internal val phase2Method: Int?
get() = if (inputMap["PH2"].isNullOrEmpty()) {
requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
WifiEnterpriseConfig.Phase2.NONE
}
} else when (inputMap["PH2"]) {
"AKA" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.AKA }
"AKA_PRIME" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.AKA_PRIME }
"GTC" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.GTC }
"MSCHAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.MSCHAP }
"MSCHAPV2" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.MSCHAPV2 }
"NONE" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.NONE }
"PAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) { WifiEnterpriseConfig.Phase2.PAP }
"SIM" -> requireSdk(Build.VERSION_CODES.O) { WifiEnterpriseConfig.Phase2.SIM }
else -> null
}
private val String.unescaped: String
get() = this.replace(escapedRegex) { escaped ->
escaped.groupValues[1]
}
private val String.quotedUnlessHex: String
get() = if (matches(hexRegex) || (startsWith("\"") &&
endsWith("\""))
) this else "\"$this\""
internal companion object {
internal fun of(inputMap: Map<String, String>): SimpleDataAccessor? {
return SimpleDataAccessor(inputMap).takeUnless {
inputMap["S"].isNullOrEmpty()
}
}
}
}
private fun WifiConfiguration.apply(
data: SimpleDataAccessor
): WifiConfiguration? {
fun WifiConfiguration.applyCommon(
data: SimpleDataAccessor
): WifiConfiguration? {
allowedAuthAlgorithms.clear()
allowedGroupCiphers.clear()
allowedKeyManagement.clear()
allowedPairwiseCiphers.clear()
allowedProtocols.clear()
SSID = data.ssid
hiddenSSID = data.hidden
return this
}
fun WifiConfiguration.applySecurity(
data: SimpleDataAccessor
): WifiConfiguration? {
when (data.securityType) {
"", "nopass" -> allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
"WEP" -> @Suppress("DEPRECATION") /* WEP as insecure */ {
data.password?.also {
wepKeys[0] = it
} ?: return null
wepTxKeyIndex = 0
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104)
}
"WPA" -> {
data.password?.also {
preSharedKey = it
} ?: return null
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
@Suppress("DEPRECATION") // WPA 1 is insecure and has bad performance
allowedProtocols.set(WifiConfiguration.Protocol.WPA) // WPA
allowedProtocols.set(WifiConfiguration.Protocol.RSN) // WPA2
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP)
@Suppress("DEPRECATION") // TKIP is insecure and has bad performance
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
}
"WPA2-EAP" -> requireSdk(Build.VERSION_CODES.JELLY_BEAN_MR2) {
data.password?.also {
preSharedKey = it
} ?: return null
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
allowedProtocols.set(WifiConfiguration.Protocol.RSN) // WPA2
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP)
@Suppress("DEPRECATION") // TKIP is insecure and has bad performance
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
enterpriseConfig.identity = data.identity
enterpriseConfig.anonymousIdentity = data.anonymousIdentity
enterpriseConfig.password = data.password
enterpriseConfig.eapMethod =
data.eapMethod ?: return null // non valid eapMethod
enterpriseConfig.phase2Method =
data.phase2Method ?: return null // non valid phase2Method
} ?: return null // api isn't high enough
}
return this
}
return this.applyCommon(data)?.applySecurity(data)
}
private inline fun <T> requireSdk(version: Int, block: () -> T): T? {
return if (Build.VERSION.SDK_INT >= version) return block() else null
}
}
@@ -1,18 +1,5 @@
package de.markusfisch.android.binaryeye.activity
import com.google.zxing.Result
import com.google.zxing.ResultMetadataType
import de.markusfisch.android.cameraview.widget.CameraView
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.hasCameraPermission
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@@ -30,10 +17,18 @@ import android.view.MenuItem
import android.view.View
import android.widget.SeekBar
import android.widget.Toast
import com.google.zxing.Result
import com.google.zxing.ResultMetadataType
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.hasCameraPermission
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.rs.Preprocessor
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.cameraview.widget.CameraView
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.IOException
class CameraActivity : AppCompatActivity() {
@@ -165,6 +160,10 @@ class CameraActivity : AppCompatActivity() {
switchCamera()
true
}
R.id.preferences -> {
startActivity(MainActivity.getPreferencesIntent(this))
true
}
R.id.info -> {
openReadme()
true
@@ -409,9 +408,7 @@ class CameraActivity : AppCompatActivity() {
}
if (returnResult) {
val resultIntent = Intent()
resultIntent.putExtra("SCAN_RESULT", result.text)
setResult(RESULT_OK, resultIntent)
setResult(RESULT_OK, getReturnIntent(result))
finish()
return
}
@@ -462,5 +459,53 @@ fun getRawBytes(result: Result): ByteArray? {
for (seg in segments as Iterable<ByteArray>) {
bytes += seg
}
return bytes
// byte segments can never be shorter than the text.
// Zxing cuts off content prefixes like "WIFI:"
return if (bytes.size >= result.text.length) bytes else null
}
fun getReturnIntent(result: Result): Intent {
val intent = Intent()
intent.putExtra("SCAN_RESULT", result.text)
intent.putExtra(
"SCAN_RESULT_FORMAT",
result.barcodeFormat.toString()
)
if (result.rawBytes?.isNotEmpty() == true) {
intent.putExtra("SCAN_RESULT_BYTES", result.rawBytes)
}
val metadata = result.resultMetadata
metadata?.let {
val orientation = metadata[ResultMetadataType.UPC_EAN_EXTENSION]
orientation?.let {
intent.putExtra(
"SCAN_RESULT_ORIENTATION",
orientation.toString()
)
}
val ecLevel = metadata[ResultMetadataType.ERROR_CORRECTION_LEVEL]
ecLevel?.let {
intent.putExtra(
"SCAN_RESULT_ERROR_CORRECTION_LEVEL",
ecLevel.toString()
)
}
val upcEanExtension = metadata[ResultMetadataType.UPC_EAN_EXTENSION]
upcEanExtension?.let {
intent.putExtra(
"SCAN_RESULT_UPC_EAN_EXTENSION",
upcEanExtension.toString()
)
}
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS]
segments?.let {
var i = 0
@Suppress("UNCHECKED_CAST")
for (seg in segments as Iterable<ByteArray>) {
intent.putExtra("SCAN_RESULT_BYTE_SEGMENTS_$i", seg)
++i
}
}
}
return intent
}
@@ -1,19 +1,19 @@
package de.markusfisch.android.binaryeye.activity
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.initSystemBars
import de.markusfisch.android.binaryeye.app.setFragment
import de.markusfisch.android.binaryeye.fragment.DecodeFragment
import de.markusfisch.android.binaryeye.fragment.EncodeFragment
import de.markusfisch.android.binaryeye.fragment.HistoryFragment
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import de.markusfisch.android.binaryeye.fragment.PreferencesFragment
class MainActivity : AppCompatActivity() {
override fun onSupportNavigateUp(): Boolean {
@@ -35,30 +35,44 @@ class MainActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (state == null) {
setFragment(supportFragmentManager, when {
intent?.hasExtra(HISTORY) == true -> HistoryFragment()
intent?.hasExtra(ENCODE) == true -> EncodeFragment.newInstance(
intent.getStringExtra(ENCODE)
)
intent?.hasExtra(DECODED_TEXT) == true -> DecodeFragment.newInstance(
intent.getStringExtra(DECODED_TEXT),
intent.getSerializableExtra(
DECODED_FORMAT
) as BarcodeFormat,
intent.getByteArrayExtra(DECODED_RAW)
)
else -> DecodeFragment()
})
setFragment(
supportFragmentManager, when {
intent?.hasExtra(PREFERENCES) == true ->
PreferencesFragment()
intent?.hasExtra(HISTORY) == true ->
HistoryFragment()
intent?.hasExtra(ENCODE) == true ->
EncodeFragment.newInstance(
intent.getStringExtra(ENCODE)
)
intent?.hasExtra(DECODED_TEXT) == true ->
DecodeFragment.newInstance(
intent.getStringExtra(DECODED_TEXT),
intent.getSerializableExtra(
DECODED_FORMAT
) as BarcodeFormat,
intent.getByteArrayExtra(DECODED_RAW)
)
else -> DecodeFragment()
}
)
}
}
companion object {
private const val PREFERENCES = "preferences"
private const val HISTORY = "history"
private const val ENCODE = "encode"
private const val DECODED_TEXT = "decoded_text"
private const val DECODED_FORMAT = "decoded_format"
private const val DECODED_RAW = "decoded_raw"
fun getPreferencesIntent(context: Context): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(PREFERENCES, true)
return intent
}
fun getHistoryIntent(context: Context): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(HISTORY, true)
@@ -73,9 +87,13 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(ENCODE, text)
if (isExternal) {
val flagActivityClearTask =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
Intent.FLAG_ACTIVITY_CLEAR_TASK
} else 0
intent.addFlags(
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_CLEAR_TASK or
flagActivityClearTask or
Intent.FLAG_ACTIVITY_NEW_TASK
)
}
@@ -1,8 +1,5 @@
package de.markusfisch.android.binaryeye.adapter
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.database.Cursor
import android.view.LayoutInflater
@@ -10,6 +7,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.CursorAdapter
import android.widget.TextView
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.data.Database
class ScansAdapter(context: Context, cursor: Cursor) :
CursorAdapter(context, cursor, false) {
@@ -44,16 +43,13 @@ class ScansAdapter(context: Context, cursor: Cursor) :
}
private fun getViewHolder(view: View): ViewHolder {
var holder = view.tag as ViewHolder?
if (holder == null) {
holder = ViewHolder(
view.findViewById(R.id.time),
view.findViewById(R.id.content),
view.findViewById(R.id.format)
)
view.tag = holder
return view.tag as ViewHolder? ?: ViewHolder(
view.findViewById(R.id.time),
view.findViewById(R.id.content),
view.findViewById(R.id.format)
).also {
view.tag = it
}
return holder
}
private data class ViewHolder(
@@ -1,10 +1,9 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.preference.Preferences
import android.app.Application
import android.support.v8.renderscript.RenderScript
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.preference.Preferences
val db = Database()
val prefs = Preferences()
@@ -1,11 +1,10 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.R
import android.annotation.SuppressLint
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentTransaction
import de.markusfisch.android.binaryeye.R
fun setFragment(fm: FragmentManager?, fragment: Fragment) {
fm?.let { getTransaction(fm, fragment).commit() }
@@ -1,10 +1,10 @@
package de.markusfisch.android.binaryeye.app
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.Manifest
fun hasCameraPermission(activity: Activity, requestCode: Int): Boolean {
return hasPermission(activity, Manifest.permission.CAMERA, requestCode)
@@ -1,20 +1,20 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.BuildConfig
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.support.v4.content.FileProvider
import android.net.Uri
import android.widget.Toast
import de.markusfisch.android.binaryeye.BuildConfig
import de.markusfisch.android.binaryeye.R
import java.io.File
fun shareText(context: Context, text: String, type: String = "text/plain") {
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_TEXT, text)
intent.type = type
context.startActivity(intent)
execShareIntent(context, intent)
}
fun shareUri(context: Context, uri: Uri, type: String) {
@@ -22,7 +22,19 @@ fun shareUri(context: Context, uri: Uri, type: String) {
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = type
context.startActivity(intent)
execShareIntent(context, intent)
}
fun execShareIntent(context: Context, intent: Intent) {
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
Toast.makeText(
context,
R.string.cannot_resolve_action,
Toast.LENGTH_SHORT
).show()
}
}
fun shareFile(context: Context, file: File, type: String) {
@@ -1,26 +1,33 @@
package de.markusfisch.android.binaryeye.app
import de.markusfisch.android.binaryeye.R
import android.content.res.Resources
import android.os.Build
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.view.Window
import de.markusfisch.android.binaryeye.R
fun initSystemBars(activity: AppCompatActivity?) {
val view = activity?.findViewById(R.id.main_layout)
if (view != null &&
setSystemBarColor(
activity.window,
ContextCompat.getColor(
activity,
R.color.primary_dark_translucent
val view = activity?.findViewById(R.id.main_layout) ?: return
setWindowInsetListener(view)
setSystemBarColor(
activity.window,
ContextCompat.getColor(activity, android.R.color.transparent)
)
}
fun setWindowInsetListener(view: View) {
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
if (insets.hasSystemWindowInsets()) {
view.setPadding(
insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom()
)
)
) {
view.setPadding(0, getStatusBarHeight(activity.resources), 0, 0)
}
insets.consumeSystemWindowInsets()
}
}
@@ -28,22 +35,11 @@ fun setSystemBarColor(window: Window, color: Int): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false
}
window.statusBarColor = color
window.navigationBarColor = color
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
return true
}
fun getStatusBarHeight(res: Resources): Int {
return getIdentifierDimen(res, "status_bar_height")
}
private fun getIdentifierDimen(res: Resources, name: String): Int {
val id = res.getIdentifier(name, "dimen", "android")
return if (id > 0) res.getDimensionPixelSize(id) else 0
}
@@ -1,13 +1,12 @@
package de.markusfisch.android.binaryeye.data
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.text.format.DateFormat
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
class Database {
private lateinit var db: SQLiteDatabase
@@ -1,27 +1,23 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.app.shareFile
import de.markusfisch.android.binaryeye.zxing.Zxing
import de.markusfisch.android.binaryeye.R
import android.os.Bundle
import android.graphics.Bitmap
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.shareFile
import de.markusfisch.android.binaryeye.zxing.Zxing
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class BarcodeFragment : Fragment() {
override fun onCreateView(
@@ -1,13 +1,6 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.hasNonPrintableCharacters
import de.markusfisch.android.binaryeye.app.hasWritePermission
import de.markusfisch.android.binaryeye.app.shareText
import de.markusfisch.android.binaryeye.R
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
@@ -19,17 +12,16 @@ import android.support.v4.app.Fragment
import android.text.ClipboardManager
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.actions.IAction
import de.markusfisch.android.binaryeye.actions.validateOrGetNew
import de.markusfisch.android.binaryeye.app.*
import java.io.File
import java.io.IOException
import java.net.URLEncoder
@@ -39,8 +31,12 @@ class DecodeFragment : Fragment() {
private lateinit var formatView: TextView
private lateinit var hexView: TextView
private lateinit var format: BarcodeFormat
private lateinit var actionMenuItem: MenuItem
private var action: IAction? = null
private var isBinary = false
private val content: String
get() = contentView.text.toString()
override fun onCreate(state: Bundle?) {
super.onCreate(state)
@@ -60,19 +56,19 @@ class DecodeFragment : Fragment() {
false
)
val content = arguments?.getString(CONTENT) ?: ""
isBinary = hasNonPrintableCharacters(content) or content.isEmpty()
val raw = arguments?.getByteArray(RAW) ?: content.toByteArray()
val inputContent = arguments?.getString(CONTENT) ?: ""
isBinary = hasNonPrintableCharacters(inputContent) or inputContent.isEmpty()
val raw = arguments?.getByteArray(RAW) ?: inputContent.toByteArray()
format = arguments?.getSerializable(FORMAT) as BarcodeFormat? ?: BarcodeFormat.QR_CODE
contentView = view.findViewById(R.id.content) as EditText
val shareFab = view.findViewById<View>(R.id.share)
contentView = view.findViewById(R.id.content)
val shareFab = view.findViewById<ImageView>(R.id.share)
if (!isBinary) {
contentView.setText(content)
contentView.setText(inputContent)
contentView.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
updateFormatAndHex(getContent().toByteArray())
updateViewsAndAction(content.toByteArray())
}
override fun beforeTextChanged(
@@ -92,12 +88,12 @@ class DecodeFragment : Fragment() {
}
})
shareFab.setOnClickListener { v ->
shareText(v.context, getContent())
shareText(v.context, content)
}
} else {
contentView.setText(R.string.binary_data)
contentView.isEnabled = false
(shareFab as ImageView).setImageResource(R.drawable.ic_action_save)
shareFab.setImageResource(R.drawable.ic_action_save)
shareFab.setOnClickListener {
askForFileNameAndSave(raw)
}
@@ -106,12 +102,13 @@ class DecodeFragment : Fragment() {
formatView = view.findViewById(R.id.format)
hexView = view.findViewById(R.id.hex)
updateFormatAndHex(raw)
updateViewsAndAction(raw)
return view
}
private fun updateFormatAndHex(bytes: ByteArray) {
private fun updateViewsAndAction(bytes: ByteArray) {
action = action.validateOrGetNew(bytes)
formatView.text = resources.getQuantityString(
R.plurals.barcode_info,
bytes.size,
@@ -119,10 +116,19 @@ class DecodeFragment : Fragment() {
bytes.size
)
hexView.text = hexDump(bytes, 33)
if (::actionMenuItem.isInitialized) {
actionMenuItem.setIcon(action?.iconResId ?: R.drawable.ic_action_open)
actionMenuItem.setTitle(action?.titleResId ?: R.string.open_url)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_decode, menu)
actionMenuItem = menu.findItem(R.id.open_url)
action?.also { action ->
actionMenuItem.setIcon(action.iconResId)
actionMenuItem.setTitle(action.titleResId)
}
if (isBinary) {
menu.findItem(R.id.copy_to_clipboard).isVisible = false
menu.findItem(R.id.open_url).isVisible = false
@@ -133,17 +139,17 @@ class DecodeFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.copy_to_clipboard -> {
copyToClipboard(getContent())
copyToClipboard(content)
true
}
R.id.open_url -> {
openUrl(getContent())
openUrl(content)
true
}
R.id.create -> {
addFragment(
fragmentManager,
EncodeFragment.newInstance(getContent(), format)
EncodeFragment.newInstance(content, format)
)
true
}
@@ -151,10 +157,6 @@ class DecodeFragment : Fragment() {
}
}
private fun getContent(): String {
return contentView.text.toString()
}
private fun copyToClipboard(text: String) {
activity ?: return
@@ -169,7 +171,11 @@ class DecodeFragment : Fragment() {
).show()
}
private fun openUrl(url: String, googleIfNoUrl: Boolean = true) {
private fun openUrl(
url: String,
executeCustomAction: Boolean = true,
searchIfNoUrl: Boolean = true
) {
if (activity == null || url.isEmpty()) {
return
}
@@ -178,18 +184,15 @@ class DecodeFragment : Fragment() {
uri = uri.normalizeScheme()
}
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(activity.packageManager) != null) {
startActivity(intent)
} else if (googleIfNoUrl) {
openUrl(
"https://www.google.com/search?q=" + URLEncoder.encode(
url,
"utf-8"
),
false
)
} else {
Toast.makeText(
when {
executeCustomAction && action != null -> action?.also { action ->
action.execute(activity, url.toByteArray())
}
intent.resolveActivity(activity.packageManager) != null -> {
startActivity(intent)
}
searchIfNoUrl -> pickSearchEngineAndSearch(activity, url)
else -> Toast.makeText(
activity,
R.string.cannot_resolve_action,
Toast.LENGTH_SHORT
@@ -197,11 +200,36 @@ class DecodeFragment : Fragment() {
}
}
private fun pickSearchEngineAndSearch(context: Context, query: String) {
val names = context.resources.getStringArray(
R.array.search_engines_names
).toMutableList()
val urls = context.resources.getStringArray(
R.array.search_engines_values
).toMutableList()
if (prefs.openWithUrl.isNotEmpty()) {
names.add(prefs.openWithUrl)
urls.add(prefs.openWithUrl)
}
AlertDialog.Builder(context)
.setTitle(R.string.pick_search_engine)
.setItems(names.toTypedArray()) { _, which ->
openUrl(
urls[which] + URLEncoder.encode(query, "utf-8"),
executeCustomAction = false,
searchIfNoUrl = false
)
}
.show()
}
// dialogs don't have a parent layout and must therefore be
// inflated with a null root layout
@SuppressLint("InflateParams")
private fun askForFileNameAndSave(raw: ByteArray) {
val ac = activity
ac ?: return
val ac = activity ?: return
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
val editText = view.findViewById(R.id.file_name) as EditText
val editText = view.findViewById<EditText>(R.id.file_name)
AlertDialog.Builder(ac)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
@@ -211,7 +239,10 @@ class DecodeFragment : Fragment() {
raw
)
if (messageId > 0) {
Toast.makeText(ac, messageId, Toast.LENGTH_SHORT).show()
Toast.makeText(
ac, messageId,
Toast.LENGTH_SHORT
).show()
}
}
}
@@ -1,23 +1,16 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.R
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.inputmethod.InputMethodManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.SeekBar
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import android.view.inputmethod.InputMethodManager
import android.widget.*
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.addFragment
class EncodeFragment : Fragment() {
private lateinit var formatView: Spinner
@@ -1,28 +1,20 @@
package de.markusfisch.android.binaryeye.fragment
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.shareText
import de.markusfisch.android.binaryeye.data.Database
import de.markusfisch.android.binaryeye.R
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.SwitchCompat
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.widget.ListView
import com.google.zxing.BarcodeFormat
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
import de.markusfisch.android.binaryeye.app.addFragment
import de.markusfisch.android.binaryeye.app.db
import de.markusfisch.android.binaryeye.app.prefs
import de.markusfisch.android.binaryeye.app.shareText
import de.markusfisch.android.binaryeye.data.Database
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -0,0 +1,46 @@
package de.markusfisch.android.binaryeye.fragment
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.SwitchCompat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import de.markusfisch.android.binaryeye.R
import de.markusfisch.android.binaryeye.app.prefs
class PreferencesFragment : Fragment() {
private lateinit var useHistorySwitch: SwitchCompat
private lateinit var openWithUrlInput: EditText
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
state: Bundle?
): View? {
activity.setTitle(R.string.preferences)
val view = inflater.inflate(
R.layout.fragment_preferences,
container,
false
)
useHistorySwitch = view.findViewById<SwitchCompat>(R.id.use_history)
if (prefs.useHistory) {
useHistorySwitch.toggle()
}
openWithUrlInput = view.findViewById<EditText>(R.id.open_with_url)
openWithUrlInput.setText(prefs.openWithUrl)
return view
}
override fun onPause() {
super.onPause()
prefs.useHistory = useHistorySwitch.isChecked()
prefs.openWithUrl = openWithUrlInput.text.toString()
}
}
@@ -12,6 +12,11 @@ class Preferences {
setBoolean(USE_HISTORY, value)
field = value
}
var openWithUrl: String = ""
set(value) {
setString(OPEN_WITH_URL, value)
field = value
}
fun init(context: Context) {
preferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -20,6 +25,7 @@ class Preferences {
fun update() {
useHistory = preferences.getBoolean(USE_HISTORY, useHistory)
openWithUrl = preferences.getString(OPEN_WITH_URL, openWithUrl)
}
private fun setBoolean(label: String, value: Boolean) {
@@ -28,7 +34,14 @@ class Preferences {
editor.apply()
}
private fun setString(label: String, value: String) {
val editor = preferences.edit()
editor.putString(label, value)
editor.apply()
}
companion object {
const val USE_HISTORY = "use_history"
const val OPEN_WITH_URL = "open_with_url"
}
}
@@ -1,13 +1,8 @@
package de.markusfisch.android.binaryeye.rs
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
import android.content.Context
import android.support.v8.renderscript.Allocation
import android.support.v8.renderscript.Element
import android.support.v8.renderscript.RenderScript
import android.support.v8.renderscript.ScriptIntrinsicResize
import android.support.v8.renderscript.Type
import android.support.v8.renderscript.*
import de.markusfisch.android.binaryeye.renderscript.ScriptC_rotator
class Preprocessor(
context: Context,
@@ -1,22 +1,9 @@
package de.markusfisch.android.binaryeye.zxing
import com.google.zxing.BarcodeFormat
import com.google.zxing.BinaryBitmap
import com.google.zxing.DecodeHintType
import com.google.zxing.LuminanceSource
import com.google.zxing.MultiFormatReader
import com.google.zxing.MultiFormatWriter
import com.google.zxing.PlanarYUVLuminanceSource
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.ReaderException
import com.google.zxing.Result
import com.google.zxing.common.HybridBinarizer
import android.graphics.Bitmap
import java.util.Arrays
import java.util.EnumMap
import java.util.EnumSet
import com.google.zxing.*
import com.google.zxing.common.HybridBinarizer
import java.util.*
class Zxing {
private val multiFormatReader: MultiFormatReader = MultiFormatReader()
@@ -137,12 +124,14 @@ class Zxing {
width: Int,
height: Int
): Bitmap? {
val hints = EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val result = MultiFormatWriter().encode(
text,
format,
width,
height,
null
hints
)
val w = result.width
val h = result.height
@@ -159,7 +148,7 @@ class Zxing {
offset += w
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
return bitmap
}
}
@@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8l8,5 8,-5v10zM12,11L4,6h16l-8,5z"/>
</vector>
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp">
<path android:fillColor="#FF000000"
android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
</vector>
@@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z"/>
</vector>
@@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
</vector>
@@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,18L6,18v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1z"/>
</vector>
@@ -0,0 +1,6 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000"
android:pathData="M17,12h-5v5h5v-5zM16,1v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2h-1L18,1h-2zM19,19L5,19L5,8h14v11z"/>
</vector>
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M1,9l2,2c4.97,-4.97 13.03,-4.97 18,0l2,-2C16.93,2.93 7.08,2.93 1,9zM9,17l3,3 3,-3c-1.65,-1.66 -4.34,-1.66 -6,0zM5,13l2,2c2.76,-2.76 7.24,-2.76 10,0l2,-2C15.14,9.14 8.87,9.14 5,13z"/>
</vector>
+4 -1
View File
@@ -1,5 +1,6 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -10,9 +11,11 @@
android:layout_marginBottom="8dp"
android:text="@string/save_as_file_name"/>
<EditText
tools:targetApi="o"
android:id="@+id/file_name"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/file_name" />
android:hint="@string/file_name"
android:importantForAutofill="no"/>
</LinearLayout>
+1 -1
View File
@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
<de.markusfisch.android.scalingimageview.widget.ScalingImageView
android:id="@+id/barcode"
android:layout_width="match_parent"
android:layout_height="match_parent"
+9 -7
View File
@@ -10,30 +10,30 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
tools:targetApi="o"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="start|top"
android:hint="@string/content"
android:imeOptions="flagNoExtractUi"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:typeface="monospace"
android:hint="@string/content"
android:importantForAutofill="no"/>
tools:ignore="UnusedAttribute"/>
<TextView
android:id="@+id/format"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:gravity="end|top"
android:textSize="14sp"/>
android:textSize="14sp" />
<TextView
android:id="@+id/hex"
android:layout_width="match_parent"
@@ -45,7 +45,9 @@
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:typeface="monospace"
android:textSize="12sp"/>
android:textSize="12sp"
tools:text="54 65 73 74 20 51 52 20 Test QR\n43 6F 64 65 Code"/>
</RelativeLayout>
</ScrollView>
<android.support.design.widget.FloatingActionButton
@@ -0,0 +1,30 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">
<android.support.v7.widget.SwitchCompat
android:id="@+id/use_history"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/use_history"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/open_with_url"/>
<EditText
tools:targetApi="o"
android:id="@+id/open_with_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:gravity="start|top"
android:typeface="monospace"
android:imeOptions="flagNoExtractUi"
android:inputType="textUri"
android:hint="@string/open_with_url_hint"
android:importantForAutofill="no"/>
</LinearLayout>
+1 -1
View File
@@ -5,4 +5,4 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary_translucent"/>
android:background="@android:color/transparent"/>
@@ -16,6 +16,11 @@
android:title="@string/switch_camera"
android:icon="@drawable/ic_action_switch_camera"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/preferences"
android:title="@string/preferences"
android:icon="@drawable/ic_action_preferences"
material:showAsAction="ifRoom"/>
<item
android:id="@+id/info"
android:title="@string/info"
+19 -1
View File
@@ -16,8 +16,9 @@
<string name="put_into_clipboard">In die Zwischenablage legen</string>
<string name="open_url">URL öffnen</string>
<string name="cannot_resolve_action">Keine App kann das öffnen</string>
<string name="pick_search_engine">Suchmaschine auswählen</string>
<string name="format">Format</string>
<string name="size">Größe in Pixeln</string>
<string name="size">Größe in Pixel</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Gib den Inhalt hier ein</string>
<string name="encode">KODIEREN</string>
@@ -27,7 +28,9 @@
<string name="info">Info</string>
<string name="switch_camera">Kamera wechseln</string>
<string name="history">Gespeicherte Codes</string>
<string name="preferences">Einstellungen</string>
<string name="use_history">Codes speichern</string>
<string name="open_with_url">URL zum Öffnen unbekannter Daten</string>
<string name="really_remove_scan">Code wirklich entfernen?</string>
<string name="really_remove_all_scans">Alle Codes entfernen?</string>
<string name="clear_history">Gespeicherte Codes löschen</string>
@@ -39,4 +42,19 @@
<string name="file_name">Dateiname</string>
<string name="error_saving_binary_data">Datei konnte nicht gespeichert werden</string>
<string name="error_file_exists">Datei existiert bereits</string>
<string name="connect_to_wifi">Mit WiFi verbinden</string>
<string name="wifi_config_failed">Konnte WiFi nicht konfigurieren</string>
<string name="wifi_added">WiFi hinzugefügt</string>
<string name="sms_send">SMS senden</string>
<string name="sms_error">SMS konnte nicht gesendet werden</string>
<string name="tel_dial">Telefonnummer wählen</string>
<string name="tel_error">Konnte Telefonnummer nicht wählen</string>
<string name="mail_send">E-Mail senden</string>
<string name="mail_error">Konnte E-Mail nicht senden</string>
<string name="vcard_add">Zu den Kontakten hinzufügen</string>
<string name="vcard_failed">Konnte nicht zu den Kontakten hinzufügen</string>
<string name="vevent_add">Zum Kalender hinzufügen</string>
<string name="vevent_failed">Konnte nicht zum Kalender hinzufügen</string>
<string name="otpauth_add">2FA hinzufügen</string>
<string name="otpauth_error">Konnte 2FA nicht hinzufügen</string>
</resources>
+18
View File
@@ -16,6 +16,7 @@
<string name="put_into_clipboard">Mis dans le presse-papiers</string>
<string name="open_url">Ouvrir l\'url</string>
<string name="cannot_resolve_action">Aucune application ne peut ouvrir ça</string>
<string name="pick_search_engine">Pick a search engine</string>
<string name="format">Format</string>
<string name="size">Taille en pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
@@ -27,7 +28,9 @@
<string name="info">Info</string>
<string name="switch_camera">Changer de camera</string>
<string name="history">Historique</string>
<string name="preferences">Preferences</string>
<string name="use_history">Sauvegarder l\'histoire des scans</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Supprimer le scan ?</string>
<string name="really_remove_all_scans">Supprimer tous les scans ?</string>
<string name="clear_history">Clear history</string>
@@ -39,4 +42,19 @@
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
+27 -9
View File
@@ -5,7 +5,7 @@
<string name="compose_barcode">Vonalkód létrehozása</string>
<string name="decode_barcode">Vonalkód visszafejtése</string>
<string name="content">Tartalom</string>
<string name="binary_data">(binary data)</string>
<string name="binary_data">(bináris adat)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d karakter</item>
<item quantity="other">%1$s\n%2$d karakter</item>
@@ -16,27 +16,45 @@
<string name="put_into_clipboard">Vágólapra helyezés</string>
<string name="open_url">URL megnyitása</string>
<string name="cannot_resolve_action">Nincs alkalmazás, amely meg tudná nyitni azt</string>
<string name="pick_search_engine">Válasszon egy keresőmotort</string>
<string name="format">Formátum</string>
<string name="size">Méret képpontban</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Itt adja meg a tartalmat</string>
<string name="encode">KÓDOLÁS</string>
<string name="error_no_content">Hiányzó tartalom</string>
<string name="error_encoding_barcode">Barcode cannot be generated</string>
<string name="error_encoding_barcode">A vonalkódot nem lehet előállítani</string>
<string name="view_barcode">Vonalkód megtekintése</string>
<string name="info">Információ</string>
<string name="switch_camera">Kamera átkapcsolása</string>
<string name="history">Előzmények</string>
<string name="preferences">Preferences</string>
<string name="use_history">Beolvasási előzmények mentése</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Valóban eltávolítja a beolvasást?</string>
<string name="really_remove_all_scans">Valóban eltávolítja az összes beolvasását?</string>
<string name="clear_history">Előzmények törlése</string>
<string name="no_barcode_found">Nem található vonalkód</string>
<string name="pick_list_separator">How to separate list items?</string>
<string name="separator_line_break">Line break</string>
<string name="separator_ruler">Ruler</string>
<string name="save_as_file_name">Save as file?</string>
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="pick_list_separator">Hogyan kell elválasztani a listaelemeket?</string>
<string name="separator_line_break">Sortörés</string>
<string name="separator_ruler">Vonalzó</string>
<string name="save_as_file_name">Menti fájlként?</string>
<string name="file_name">Fájlnév</string>
<string name="error_saving_binary_data">Nem lehet elmenteni a fájlt</string>
<string name="error_file_exists">A fájl már létezik</string>
<string name="connect_to_wifi">Kapcsolódás WiFi-hez</string>
<string name="wifi_config_failed">Nem sikerült beállítani a WiFi-t</string>
<string name="wifi_added">WiFi hozzáadva</string>
<string name="sms_send">SMS küldése</string>
<string name="sms_error">Nem sikerült elküldeni az SMS-t</string>
<string name="tel_dial">Szám tárcsázása</string>
<string name="tel_error">Nem sikerült tárcsázni a számot</string>
<string name="mail_send">Levél küldése</string>
<string name="mail_error">Nem sikerült elküldeni a levelet</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
+60
View File
@@ -0,0 +1,60 @@
<resources>
<string name="no_camera_no_fun">Aplikasi tidak akan bisa bekerja tanpa akses kamera. Selamat tinggal.</string>
<string name="camera_error">Tidak bisa mengakses kamera, silakan coba lagi</string>
<string name="scan_code">Pindai kode</string>
<string name="compose_barcode">Buat barcode</string>
<string name="decode_barcode">Dekode barcode</string>
<string name="content">Konten</string>
<string name="binary_data">(data binari)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d karakter</item>
<item quantity="other">%1$s\n%2$d karakter</item>
</plurals>
<string name="toggle_flash">Nyala/Matikan blitz</string>
<string name="share">Bagikan</string>
<string name="copy_to_clipboard">Salin ke papan klip</string>
<string name="put_into_clipboard">Simpan ke papan klip</string>
<string name="open_url">Buka url</string>
<string name="cannot_resolve_action">Tidak ada aplikasi yang bisa membukanya</string>
<string name="pick_search_engine">Pilih mesin pencari</string>
<string name="format">Format</string>
<string name="size">Ukuran dalam piksel</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Masukkan konten di sini</string>
<string name="encode">ENKODE</string>
<string name="error_no_content">Tidak ada konten</string>
<string name="error_encoding_barcode">Barcode tidak bisa dibuat</string>
<string name="view_barcode">Lihat barcode</string>
<string name="info">Info</string>
<string name="switch_camera">Ganti kamera</string>
<string name="history">Riwayat</string>
<string name="preferences">Preferensi</string>
<string name="use_history">Simpan riwayat pemindaian</string>
<string name="open_with_url">Buka data tak dikenal dengan URL</string>
<string name="really_remove_scan">Yakin menghapus pemindaian?</string>
<string name="really_remove_all_scans">Yakin menghapus semua pemindaian?</string>
<string name="clear_history">Hapus riwayat</string>
<string name="no_barcode_found">Tidak ada barcode yang ditemukan</string>
<string name="pick_list_separator">Pilih pemisah daftar item?</string>
<string name="separator_line_break">Baris baru</string>
<string name="separator_ruler">Penggaris</string>
<string name="save_as_file_name">Simpan sebagai berkas?</string>
<string name="file_name">Nama berkas</string>
<string name="error_saving_binary_data">Tidak bisa menyimpan berkas</string>
<string name="error_file_exists">Berkas sudah ada</string>
<string name="connect_to_wifi">Sambungkan ke WiFi</string>
<string name="wifi_config_failed">Tidak bisa mengelola WiFi</string>
<string name="wifi_added">WiFi ditambahkan</string>
<string name="sms_send">Kirim SMS</string>
<string name="sms_error">Tidak bisa mengirim SMS</string>
<string name="tel_dial">Panggil nomor</string>
<string name="tel_error">Tidak bisa memanggil nomor</string>
<string name="mail_send">Kirim surel</string>
<string name="mail_error">Tidak bisa mengirim surel</string>
<string name="vcard_add">Tambahkan ke kontak</string>
<string name="vcard_failed">Tidak bisa menambahkan ke kontak</string>
<string name="vevent_add">Tambahkan ke kalender</string>
<string name="vevent_failed">Tidak bisa menambahkan ke kalender</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
+18
View File
@@ -16,6 +16,7 @@
<string name="put_into_clipboard">Metti negli appunti</string>
<string name="open_url">Apri URL</string>
<string name="cannot_resolve_action">Nessuna app può aprirlo</string>
<string name="pick_search_engine">Pick a search engine</string>
<string name="format">Formato</string>
<string name="size">Dimensioni in pixel</string>
<string name="width_by_height">%1$d×%2$d</string>
@@ -27,7 +28,9 @@
<string name="info">Info</string>
<string name="switch_camera">Cambia camera</string>
<string name="history">Cronologia</string>
<string name="preferences">Preferences</string>
<string name="use_history">Salva cronologia delle scansioni</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Vuoi veramente rimuovere la scansione?</string>
<string name="really_remove_all_scans">Vuoi veramente rimuovere tutte le scansioni?</string>
<string name="clear_history">Pulisci cronologia</string>
@@ -39,4 +42,19 @@
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
+18
View File
@@ -16,6 +16,7 @@
<string name="put_into_clipboard">Kopieer de inhoud naar het klembord</string>
<string name="open_url">URL openen</string>
<string name="cannot_resolve_action">Er is geen app aangetroffen die dit kan openen</string>
<string name="pick_search_engine">Pick a search engine</string>
<string name="format">Formaat</string>
<string name="size">Grootte, in pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
@@ -27,7 +28,9 @@
<string name="info">Informatie</string>
<string name="switch_camera">Camera wisselen</string>
<string name="history">History</string>
<string name="preferences">Preferences</string>
<string name="use_history">Save scan history</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="clear_history">Clear history</string>
@@ -39,4 +42,19 @@
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
@@ -0,0 +1,60 @@
<resources>
<string name="no_camera_no_fun">Esse aplicativo não faz sentido de uso sem acesso para a câmera. Adeus.</string>
<string name="camera_error">Sem acesso a câmera, por favor tente novamente</string>
<string name="scan_code">Escaneie o código de barras</string>
<string name="compose_barcode">Compor código de barras</string>
<string name="decode_barcode">Decodificar código de barras</string>
<string name="content">Conteúdo</string>
<string name="binary_data">(binários)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d cacractere</item>
<item quantity="other">%1$s\n%2$d caracteres</item>
</plurals>
<string name="toggle_flash">Ativar lanterna</string>
<string name="share">Compartilhar</string>
<string name="copy_to_clipboard">Copiar para área de transferência</string>
<string name="put_into_clipboard">Colocar na área de transferência</string>
<string name="open_url">Abrir url</string>
<string name="cannot_resolve_action">Nenhuma aplicativo pode abrir isso</string>
<string name="pick_search_engine">Escolha um mecanismo de busca</string>
<string name="format">Formatar</string>
<string name="size">Tamanho em pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">Insira o conteúdo aqui</string>
<string name="encode">CODIFICAR</string>
<string name="error_no_content">Conteúdo não disponível</string>
<string name="error_encoding_barcode">Não foi possível gerar o código de barras</string>
<string name="view_barcode">Ver código de barras</string>
<string name="info">Informações</string>
<string name="switch_camera">Trocar câmera</string>
<string name="history">Histórico</string>
<string name="preferences">Preferências</string>
<string name="use_history">Salvar histórico de escaneamento</string>
<string name="open_with_url">Abrir dados desconhecidos com URL</string>
<string name="really_remove_scan">Realmente deseja remover o escaneamento?</string>
<string name="really_remove_all_scans">Realmente deseja remover todos escaneamentos?</string>
<string name="clear_history">Limpar histórico</string>
<string name="no_barcode_found">Código de barras não encontrado</string>
<string name="pick_list_separator">Como separar itens da lista?</string>
<string name="separator_line_break">Quebra de linha</string>
<string name="separator_ruler">Régua</string>
<string name="save_as_file_name">Salvar como arquivo de imagem?</string>
<string name="file_name">Nome do arquivo</string>
<string name="error_saving_binary_data">Arquivo não pode ser salvo</string>
<string name="error_file_exists">Arquivo já existe</string>
<string name="connect_to_wifi">Conectar para o WiFi</string>
<string name="wifi_config_failed">Não foi possível configurar o WiFi</string>
<string name="wifi_added">WiFi adicionado</string>
<string name="sms_send">Enviar SMS</string>
<string name="sms_error">Não foi possível enviar o SMS</string>
<string name="tel_dial">Discar número</string>
<string name="tel_error">Não foi possível discar o número</string>
<string name="mail_send">Enviar e-mail</string>
<string name="mail_error">Não foi possível enviar o e-mail</string>
<string name="vcard_add">Adicionar para os contatos</string>
<string name="vcard_failed">NNão foi possível adicionar para os contatos</string>
<string name="vevent_add">Adicionar ao calendário</string>
<string name="vevent_failed">Não foi possível adicionar ao calendário</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
@@ -0,0 +1,60 @@
<resources>
<string name="no_camera_no_fun">没有相机权限,这个软件有啥意义。再见!</string>
<string name="camera_error">无法访问相机,请重试。</string>
<string name="scan_code">Scan code</string>
<string name="compose_barcode">制作条形码</string>
<string name="decode_barcode">解码条形码</string>
<string name="content">内容</string>
<string name="binary_data">(二进制数据)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d 字符</item>
<item quantity="other">%1$s\n%2$d 字符</item>
</plurals>
<string name="toggle_flash">切换闪光灯</string>
<string name="share">分享</string>
<string name="copy_to_clipboard">复制到剪切板</string>
<string name="put_into_clipboard">复制到剪切板</string>
<string name="open_url">打开链接</string>
<string name="cannot_resolve_action">没有应用程序可以打开它</string>
<string name="pick_search_engine">选择一个搜索引擎</string>
<string name="format">编码格式</string>
<string name="size">图片尺寸</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">在这里输入内容</string>
<string name="encode">编码</string>
<string name="error_no_content">缺少内容</string>
<string name="error_encoding_barcode">无法生成条形码</string>
<string name="view_barcode">查看条形码</string>
<string name="info">信息</string>
<string name="switch_camera">切换摄像头</string>
<string name="history">历史</string>
<string name="preferences">首选项</string>
<string name="use_history">保存扫描历史</string>
<string name="open_with_url">使用URL打开未知数据</string>
<string name="really_remove_scan">确定删除记录?</string>
<string name="really_remove_all_scans">确定删除全部记录?</string>
<string name="clear_history">删除记录</string>
<string name="no_barcode_found">未找到条形码</string>
<string name="pick_list_separator">如何分隔列表项?</string>
<string name="separator_line_break"></string>
<string name="separator_ruler">分割线</string>
<string name="save_as_file_name">保存为文件?</string>
<string name="file_name">文件名</string>
<string name="error_saving_binary_data">无法保存文件</string>
<string name="error_file_exists">文件已存在</string>
<string name="connect_to_wifi">连接到WiFi</string>
<string name="wifi_config_failed">无法配置WiFi</string>
<string name="wifi_added">WiFi已添加</string>
<string name="sms_send">发送短信</string>
<string name="sms_error">无法发送短信</string>
<string name="tel_dial">拨打号码</string>
<string name="tel_error">无法拨打此号码</string>
<string name="mail_send">发送邮件</string>
<string name="mail_error">无法发送邮件</string>
<string name="vcard_add">添加到通讯录</string>
<string name="vcard_failed">无法添加到通讯录</string>
<string name="vevent_add">添加到日历</string>
<string name="vevent_failed">无法添加到日历</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
+60
View File
@@ -0,0 +1,60 @@
<resources>
<string name="no_camera_no_fun">沒有相機權限的話,這個程式根本什麽都做不了。再見!</string>
<string name="camera_error">不能開啓相機,請重試</string>
<string name="scan_code">掃描條碼</string>
<string name="compose_barcode">製作條碼</string>
<string name="decode_barcode">解讀條碼</string>
<string name="content">内容</string>
<string name="binary_data">(2進制碼)</string>
<plurals name="barcode_info">
<item quantity="one">%1$s\n%2$d 個字符</item>
<item quantity="other">%1$s\n%2$d 個字符</item>
</plurals>
<string name="toggle_flash">閃光燈</string>
<string name="share">分享</string>
<string name="copy_to_clipboard">複製</string>
<string name="put_into_clipboard">複製</string>
<string name="open_url">開啓</string>
<string name="cannot_resolve_action">無應用程式可以開啓</string>
<string name="pick_search_engine">選擇搜尋引擎</string>
<string name="format">格式</string>
<string name="size">大小(pixels)</string>
<string name="width_by_height">%1$d×%2$d</string>
<string name="input_content_here">在此輸入</string>
<string name="encode">製作條碼</string>
<string name="error_no_content">找不到内容</string>
<string name="error_encoding_barcode">條碼製作失敗</string>
<string name="view_barcode">檢視條碼</string>
<string name="info">資訊</string>
<string name="switch_camera">切換相機</string>
<string name="history">歷史</string>
<string name="use_history">儲存掃描歷史</string>
<string name="really_remove_scan">真的要移除掃描記錄?</string>
<string name="really_remove_all_scans">真的要移除所有掃描記錄?</string>
<string name="clear_history">清除記錄</string>
<string name="no_barcode_found">找不到條碼</string>
<string name="pick_list_separator">如何分開列表項目?</string>
<string name="separator_line_break">Line break</string>
<string name="separator_ruler">直尺</string>
<string name="save_as_file_name">儲存成檔案?</string>
<string name="file_name">檔案名稱</string>
<string name="error_saving_binary_data">不能儲存檔案</string>
<string name="error_file_exists">檔案已存在</string>
<string name="connect_to_wifi">連接至 WiFi</string>
<string name="wifi_config_failed">WiFi連接設定失敗</string>
<string name="wifi_added">成功添加WiFi</string>
<string name="sms_send">傳送SMS</string>
<string name="sms_error">不能傳送SMS</string>
<string name="tel_dial">撥號</string>
<string name="tel_error">不能撥號</string>
<string name="mail_send">發送電郵</string>
<string name="mail_error">不能發送電郵</string>
<string name="vcard_add">添加至聯絡人</string>
<string name="vcard_failed">不能添加至聯絡人</string>
<string name="vevent_add">添加至行事曆</string>
<string name="vevent_failed">不能添加至行事曆</string>
<string name="preferences">Preferences</string>
<string name="open_with_url">Preferences</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
-2
View File
@@ -1,8 +1,6 @@
<resources>
<color name="primary">#222</color>
<color name="primary_translucent">#8222</color>
<color name="primary_dark">#111</color>
<color name="primary_dark_translucent">#8111</color>
<color name="accent">#b6d46f</color>
<color name="accent_dark">#a6c45f</color>
<color name="background_color">#111</color>
@@ -1,3 +1,4 @@
<resources>
<string name="app_name">Binary Eye</string>
<string name="open_with_url_hint">http://example.com?data=</string>
</resources>
@@ -0,0 +1,18 @@
<resources>
<string-array name="search_engines_names">
<item>Google</item>
<item>DuckDuckGo</item>
<item>Qwant</item>
<item>OpenFoodFacts.org</item>
<item>OpenBeautyFacts.org</item>
<item>OpenPetFoodFacts.org</item>
</string-array>
<string-array name="search_engines_values">
<item>https://www.google.com/search?q=</item>
<item>https://duckduckgo.com/?q=</item>
<item>https://www.qwant.com/?q=</item>
<item>https://world.openfoodfacts.org/cgi/search.pl?search_terms=</item>
<item>https://world.openbeautyfacts.org/cgi/search.pl?search_terms=</item>
<item>https://world.openpetfoodfacts.org/cgi/search.pl?search_terms=</item>
</string-array>
</resources>
+18
View File
@@ -16,6 +16,7 @@
<string name="put_into_clipboard">Put into clipboard</string>
<string name="open_url">Open url</string>
<string name="cannot_resolve_action">No application can open that</string>
<string name="pick_search_engine">Pick a search engine</string>
<string name="format">Format</string>
<string name="size">Size in pixels</string>
<string name="width_by_height">%1$d×%2$d</string>
@@ -27,7 +28,9 @@
<string name="info">Info</string>
<string name="switch_camera">Switch camera</string>
<string name="history">History</string>
<string name="preferences">Preferences</string>
<string name="use_history">Save scan history</string>
<string name="open_with_url">Open unknown data with URL</string>
<string name="really_remove_scan">Really remove scan?</string>
<string name="really_remove_all_scans">Really remove all scans?</string>
<string name="clear_history">Clear history</string>
@@ -39,4 +42,19 @@
<string name="file_name">File name</string>
<string name="error_saving_binary_data">Cannot save file</string>
<string name="error_file_exists">File already exists</string>
<string name="connect_to_wifi">Connect to WiFi</string>
<string name="wifi_config_failed">Could not configure WiFi</string>
<string name="wifi_added">WiFi added</string>
<string name="sms_send">Send SMS</string>
<string name="sms_error">Could not send SMS</string>
<string name="tel_dial">Dial number</string>
<string name="tel_error">Could not dial number</string>
<string name="mail_send">Send mail</string>
<string name="mail_error">Could not send mail</string>
<string name="vcard_add">Add to contacts</string>
<string name="vcard_failed">Could not add to contacts</string>
<string name="vevent_add">Add to calendar</string>
<string name="vevent_failed">Could not add to calendar</string>
<string name="otpauth_add">Add 2FA</string>
<string name="otpauth_error">Could not add 2FA</string>
</resources>
@@ -0,0 +1,19 @@
package de.markusfisch.android.binaryeye
import junit.framework.TestCase.fail
fun simpleFail(message: String = "Unknown reason, but failed"): Nothing {
fail(message)
throw IllegalStateException("You should never have reached this point in the code, but anyways: $message")
}
inline fun <reified E : Throwable> assertThrows(message: String = "Unknown reason, but didn't failed even though should fail", block: () -> Unit) {
try {
block()
fail(message)
} catch (e: Throwable) {
if (e !is E) {
fail("Got unexpected other Throwable: ${e::class.java.simpleName}\n$e")
}
}
}
@@ -0,0 +1,139 @@
package de.markusfisch.android.binaryeye.actions.otpauth
import de.markusfisch.android.binaryeye.assertThrows
import de.markusfisch.android.binaryeye.simpleFail
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNull
import org.junit.Test
class OtpauthParserTest {
@Test
fun noOtpauthUri() {
assertNull(OtpauthParser("random string"))
}
@Test
fun missingSecret() {
assertNull(OtpauthParser("otpauth://totp/issuerName:accountName"))
}
@Test
fun missingCounterHotp() {
assertNull(OtpauthParser("otpauth://hotp/accountName?secret=secretString"))
}
@Test
fun minimalHotp() {
val parser = validParser("otpauth://hotp/accountName?secret=secretString&counter=50")
assertEquals("hotp", parser.otpType)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA1", parser.algorithm)
assertEquals(6, parser.digits)
assertEquals(30, parser.period)
assertEquals(50, parser.counter)
}
@Test
fun normalHotp() {
val parser = validParser("otpauth://hotp/issuerName:accountName?secret=secretString&counter=0")
assertEquals("hotp", parser.otpType)
assertEquals("issuerName", parser.issuer)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA1", parser.algorithm)
assertEquals(6, parser.digits)
assertEquals(30, parser.period)
assertEquals(0, parser.counter)
}
@Test
fun missingIssuerParamHotp() {
val parser = validParser("otpauth://hotp/issuerName:accountName?secret=secretString&counter=50&algorithm=SHA256&digits=8")
assertEquals("hotp", parser.otpType)
assertEquals("issuerName", parser.issuer)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA256", parser.algorithm)
assertEquals(8, parser.digits)
assertEquals(30, parser.period)
assertEquals(50, parser.counter)
}
@Test
fun fullHotp() {
val parser = validParser("otpauth://hotp/issuerName:accountName?secret=secretString&counter=50&issuer=issuerName&algorithm=SHA256&digits=8")
assertEquals("hotp", parser.otpType)
assertEquals("issuerName", parser.issuer)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA256", parser.algorithm)
assertEquals(8, parser.digits)
assertEquals(30, parser.period)
assertEquals(50, parser.counter)
}
@Test
fun minimalTotp() {
val parser = validParser("otpauth://totp/accountName?secret=secretString")
assertEquals("totp", parser.otpType)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA1", parser.algorithm)
assertEquals(6, parser.digits)
assertEquals(30, parser.period)
assertThrows<NumberFormatException> {
parser.counter
}
}
@Test
fun normalTotp() {
val parser = validParser("otpauth://totp/issuerName:accountName?secret=secretString")
assertEquals("totp", parser.otpType)
assertEquals("issuerName", parser.issuer)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA1", parser.algorithm)
assertEquals(6, parser.digits)
assertEquals(30, parser.period)
assertThrows<NumberFormatException> {
parser.counter
}
}
@Test
fun missingIssuerParamTotp() {
val parser = validParser("otpauth://totp/issuerName:accountName?secret=secretString&algorithm=SHA256&digits=8&period=60")
assertEquals("totp", parser.otpType)
assertEquals("issuerName", parser.issuer)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA256", parser.algorithm)
assertEquals(8, parser.digits)
assertEquals(60, parser.period)
assertThrows<NumberFormatException> {
parser.counter
}
}
@Test
fun fullTotp() {
val parser = validParser("otpauth://totp/issuerName:accountName?secret=secretString&issuer=issuerName&algorithm=SHA256&digits=8&period=60")
assertEquals("totp", parser.otpType)
assertEquals("issuerName", parser.issuer)
assertEquals("accountName", parser.accountName)
assertEquals("secretString", parser.secret)
assertEquals("SHA256", parser.algorithm)
assertEquals(8, parser.digits)
assertEquals(60, parser.period)
assertThrows<NumberFormatException> {
parser.counter
}
}
private fun validParser(input: String): OtpauthParser {
return OtpauthParser(input) ?: simpleFail("Could not get parser of valid input")
}
}
@@ -0,0 +1,93 @@
package de.markusfisch.android.binaryeye.actions.wifi
import de.markusfisch.android.binaryeye.simpleFail
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import org.junit.Test
class WifiConfigurationFactoryTest {
@Test
fun notWifi() {
assertNull(WifiConfigurationFactory.parseMap("asdfz"))
}
@Test
fun wep() {
val info = simpleDataAccessor("WIFI:T:WEP;S:asdfz;P:password;;")
assertEquals("WEP", info.securityType)
assertEquals("\"asdfz\"", info.ssid)
assertEquals("\"password\"", info.password)
assertFalse(info.hidden)
}
@Test
fun hidden() {
val info = simpleDataAccessor("WIFI:T:WPA;S:asdfz;P:password;H:true;;")
assertEquals("WPA", info.securityType)
assertEquals("\"asdfz\"", info.ssid)
assertEquals("\"password\"", info.password)
assertTrue(info.hidden)
}
@Test
fun nopass() {
val info = simpleDataAccessor("WIFI:T:nopass;S:asdfz;;")
assertEquals("nopass", info.securityType)
assertEquals("\"asdfz\"", info.ssid)
assertNull(info.password)
assertFalse(info.hidden)
}
@Test
fun plainUnsecured() {
val info = simpleDataAccessor("WIFI:S:asdfz;;")
assertEquals("", info.securityType)
assertEquals("\"asdfz\"", info.ssid)
assertNull(info.password)
assertFalse(info.hidden)
}
@Test
fun hex() {
val info = simpleDataAccessor("WIFI:T:WEP;S:d34dbeef;P:d34dbeef;;")
assertEquals("WEP", info.securityType)
assertEquals("d34dbeef", info.ssid)
assertEquals("d34dbeef", info.password)
assertFalse(info.hidden)
}
@Test
fun escaping() {
val info = simpleDataAccessor("""WIFI:S:\"ssid\\\;stillSSID\:\;x;;""")
assertEquals("", info.securityType)
assertEquals("""""ssid\;stillSSID:;x"""", info.ssid)
assertNull(info.password)
assertFalse(info.hidden)
}
@Test
fun wrongEscaping() {
val info = simpleDataAccessor("""WIFI:S:\SSID":\;x;""")
assertEquals("", info.securityType)
assertEquals(""""\SSID":;x"""", info.ssid)
assertNull(info.password)
assertFalse(info.hidden)
}
private fun simpleDataAccessor(wifiString: String): WifiConfigurationFactory.SimpleDataAccessor {
val map = WifiConfigurationFactory.parseMap(wifiString)
?: simpleFail("parsing map of valid string fails ($wifiString)")
return WifiConfigurationFactory.SimpleDataAccessor.of(map)
?: simpleFail("could not create SimpleDataAccessor of (potentially) valid map ($map of $wifiString)")
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
buildscript {
ext.kotlin_version = '1.3.31'
ext.tools_version = '3.4.1'
ext.tools_version = '3.4.2'
ext.build_tools_version = '28.0.3'
ext.sdk_version = 28
ext.support_version = '25.3.1'