Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee4ee1f778 | ||
|
|
dbb72747e9 | ||
|
|
8e577c0924 | ||
|
|
6ce160447c | ||
|
|
b94a47ccb4 | ||
|
|
ec34b33ce7 | ||
|
|
024b95c12e | ||
|
|
cf4dc2aa4a | ||
|
|
1919ce2935 | ||
|
|
1e3dfb64ae | ||
|
|
f87eaa82dd | ||
|
|
13b90186d3 | ||
|
|
2c4e329663 | ||
|
|
624e129ef7 | ||
|
|
2f19bd7fc9 | ||
|
|
da48539585 | ||
|
|
a1d263837a | ||
|
|
188824ecaa | ||
|
|
f5376290ab | ||
|
|
b888e37556 | ||
|
|
cb43160902 | ||
|
|
a3ad6aa4c5 | ||
|
|
d0f0bedae2 | ||
|
|
3f58349ce8 | ||
|
|
e90d7d5f0e | ||
|
|
b39e5d2721 | ||
|
|
3ede2713b6 | ||
|
|
dc2fc292ce | ||
|
|
3dc50d2d7a | ||
|
|
3d15425460 | ||
|
|
412e27ee81 | ||
|
|
d73ea341c0 | ||
|
|
694ebf83ed | ||
|
|
78bb03fabf | ||
|
|
902215f064 | ||
|
|
eaf38729bd | ||
|
|
bdd89ca539 | ||
|
|
39ecd31455 | ||
|
|
c96a2cf3ec | ||
|
|
ea9e29b98c | ||
|
|
96e1ef5a52 | ||
|
|
ed4b0a75dd | ||
|
|
37354338cb | ||
|
|
3cd2eecc22 | ||
|
|
0e40d3f7c3 | ||
|
|
c8fa01116d | ||
|
|
04539b4c37 | ||
|
|
b9e8f4cfca | ||
|
|
ad06c2bf9e | ||
|
|
2c7ec74b1b | ||
|
|
b34f29c95f | ||
|
|
e11518c3f5 | ||
|
|
57c4fad307 | ||
|
|
856b8e37e1 | ||
|
|
6b8dd8e864 | ||
|
|
c993cd53f1 | ||
|
|
934983f668 | ||
|
|
ccf425faad | ||
|
|
a244763109 | ||
|
|
7ab712e040 | ||
|
|
cb7219b290 | ||
|
|
7f4ba50ee4 | ||
|
|
b1f00f0592 | ||
|
|
80f8f908d2 | ||
|
|
506308ece9 | ||
|
|
baecded9d5 | ||
|
|
ceefee397f | ||
|
|
e45804be31 | ||
|
|
edef1acbfb | ||
|
|
742939182c | ||
|
|
a21bfcdea6 | ||
|
|
bd281e449b | ||
|
|
0e1464a15f | ||
|
|
8a6441ddf3 | ||
|
|
94874c0a9e | ||
|
|
b9a6410959 | ||
|
|
512f5e00dd | ||
|
|
91c2364958 | ||
|
|
1e3f1c553c | ||
|
|
80c65a48a0 | ||
|
|
4c04ffb7db | ||
|
|
cc3949fa71 | ||
|
|
904a0ecbdd | ||
|
|
9155dd026d | ||
|
|
7286a06985 | ||
|
|
a71552a406 | ||
|
|
5857e8c394 | ||
|
|
429af04714 | ||
|
|
f134987572 | ||
|
|
d8a9043f94 | ||
|
|
3782fa8360 | ||
|
|
023e32f796 | ||
|
|
82f3abdc86 | ||
|
|
1345d31a6d | ||
|
|
c56fa39c15 | ||
|
|
7c8997ae9a | ||
|
|
584343c4e9 |
@@ -1,5 +1,72 @@
|
||||
# Change Log
|
||||
|
||||
# 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.8.1
|
||||
* Distribute APK instead of App Bundle
|
||||
|
||||
## 1.8.0
|
||||
* Save binary data into external file
|
||||
* Fix storage of binary data
|
||||
* Remove horizontal orientation line from scanner
|
||||
|
||||
## 1.7.3
|
||||
* Fix crashes on Lineage and remove RenderScript fallback
|
||||
* Improve handling of binary data
|
||||
|
||||
## 1.7.2
|
||||
* Add a fallback for RenderScript
|
||||
* Add italian translation
|
||||
|
||||
## 1.7.1
|
||||
* Fix support for 64 bit architectures
|
||||
|
||||
## 1.7.0
|
||||
* Pick list separator on sharing history listing
|
||||
* Update Hungarian translation
|
||||
|
||||
## 1.6.0
|
||||
* Share history listing as plain text
|
||||
* Add separator line between history items
|
||||
* Add content description for floating action buttons
|
||||
|
||||
## 1.5.1
|
||||
* Fix generating hex dump
|
||||
|
||||
## 1.5.0
|
||||
* Show a hex dump of decoded contents
|
||||
* Fix processing text from share menu
|
||||
* Add French translation
|
||||
|
||||
## 1.4.3
|
||||
* Fix broken history listing
|
||||
|
||||
## 1.4.2
|
||||
* Show barcode type and content length after decoding
|
||||
|
||||
## 1.4.1
|
||||
* Toggle availability of torch mode button according to camera features
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
Please try to keep things in good shape and comply to what's there.
|
||||
|
||||
This project follows Android [best practices][android_best_practices]
|
||||
so please have a look if you've never heard of them.
|
||||
|
||||
The code is formatted according to Android Studio's standard, with the
|
||||
exception of indent being tabs instead of spaces.
|
||||
|
||||
Use the feature branch workflow to add new features and make sure
|
||||
to squash when merging into master:
|
||||
|
||||
$ git merge cool_feature --squash
|
||||
|
||||
Then write a [good commit messages][commit_messages] to keep the history
|
||||
meaningful and useful. One feature, one commit.
|
||||
|
||||
[android_best_practices]: https://developer.android.com/distribute/best-practices/develop/
|
||||
[commit_messages]: https://juffalow.com/other/write-good-git-commit-message
|
||||
@@ -1,5 +1,4 @@
|
||||
PACKAGE = de.markusfisch.android.binaryeye
|
||||
APK = app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
all: debug install start
|
||||
|
||||
@@ -7,11 +6,10 @@ debug:
|
||||
./gradlew assembleDebug
|
||||
|
||||
release: lint
|
||||
@./gradlew assembleRelease \
|
||||
-Pandroid.injected.signing.store.file=$(ANDROID_KEYFILE) \
|
||||
-Pandroid.injected.signing.store.password=$(ANDROID_STORE_PASSWORD) \
|
||||
-Pandroid.injected.signing.key.alias=$(ANDROID_KEY_ALIAS) \
|
||||
-Pandroid.injected.signing.key.password=$(ANDROID_KEY_PASSWORD)
|
||||
./gradlew assembleRelease
|
||||
|
||||
bundle: lint
|
||||
./gradlew bundleRelease
|
||||
|
||||
lint:
|
||||
./gradlew lintDebug
|
||||
@@ -23,10 +21,10 @@ infer: clean
|
||||
infer -- ./gradlew assembleDebug
|
||||
|
||||
test:
|
||||
./gradlew cAT
|
||||
./gradlew test cAT
|
||||
|
||||
install:
|
||||
adb $(TARGET) install -r $(APK)
|
||||
adb $(TARGET) install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
start:
|
||||
adb $(TARGET) shell 'am start -n \
|
||||
|
||||
@@ -9,7 +9,7 @@ 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
|
||||
a barcode.
|
||||
|
||||
<a href="https://f-droid.org/en/packages/de.markusfisch.android.binaryeye/"><img alt="Get it on F-Droid" src="https://f-droid.org/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>
|
||||
<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.
|
||||
|
||||
+19
-12
@@ -1,10 +1,5 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'net.ltgt.errorprone'
|
||||
|
||||
configurations.errorprone {
|
||||
resolutionStrategy.force 'com.google.errorprone:error_prone_core:2.0.19'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion sdk_version
|
||||
@@ -14,19 +9,31 @@ android {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion sdk_version
|
||||
|
||||
versionCode 16
|
||||
versionName '1.4.1'
|
||||
versionCode 33
|
||||
versionName '1.11.2'
|
||||
|
||||
// it's recommended to set this value to the lowest API level
|
||||
// able to provide all the functionality
|
||||
renderscriptTargetApi 18
|
||||
renderscriptTargetApi 24
|
||||
renderscriptSupportModeEnabled true
|
||||
|
||||
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
config {
|
||||
keyAlias System.getenv('ANDROID_KEY_ALIAS')
|
||||
keyPassword System.getenv('ANDROID_KEY_PASSWORD')
|
||||
storePassword System.getenv('ANDROID_STORE_PASSWORD')
|
||||
def filePath = System.getenv('ANDROID_KEYFILE')
|
||||
storeFile filePath ? file(filePath) : null
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
androidTest.java.srcDirs += 'src/androidTest/kotlin'
|
||||
test.java.srcDirs += 'src/test/kotlin'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -35,11 +42,10 @@ android {
|
||||
}
|
||||
|
||||
release {
|
||||
proguardFiles \
|
||||
getDefaultProguardFile('proguard-android.txt'), \
|
||||
'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
signingConfig signingConfigs.config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,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.2'
|
||||
implementation 'com.github.markusfisch:CameraView:1.8.4'
|
||||
implementation 'com.github.markusfisch:ScalingImageView:1.1.3'
|
||||
}
|
||||
|
||||
@@ -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,18 @@
|
||||
package de.markusfisch.android.binaryeye.actions
|
||||
|
||||
import de.markusfisch.android.binaryeye.actions.mail.MailAction
|
||||
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, SmsAction, TelAction, VCardAction, VEventAction, WifiAction
|
||||
)
|
||||
|
||||
fun getAction(data: ByteArray): IAction? = REGISTRY.find {
|
||||
it.canExecuteOn(data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.markusfisch.android.binaryeye.actions
|
||||
|
||||
import de.markusfisch.android.binaryeye.app.execShareIntent
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
|
||||
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,31 @@
|
||||
package de.markusfisch.android.binaryeye.actions.mail
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
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,34 @@
|
||||
package de.markusfisch.android.binaryeye.actions.sms
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
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,24 @@
|
||||
package de.markusfisch.android.binaryeye.actions.tel
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
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,29 @@
|
||||
package de.markusfisch.android.binaryeye.actions.vtype
|
||||
|
||||
object VTypeParser {
|
||||
private val vTypeRegex =
|
||||
"""^BEGIN:(VCARD|VEVENT)(?:\R.+?:[\s\S]+?)+?\REND:\1\R?$""".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)
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package de.markusfisch.android.binaryeye.actions.vtype.vcard
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.ContactsContract
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package de.markusfisch.android.binaryeye.actions.vtype.vevent
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.SimpleIntentIAction
|
||||
import de.markusfisch.android.binaryeye.actions.vtype.VTypeParser
|
||||
|
||||
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 java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
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,97 @@
|
||||
package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IAction
|
||||
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
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,10 +1,12 @@
|
||||
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
|
||||
@@ -21,8 +23,6 @@ import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.os.Vibrator
|
||||
import android.provider.MediaStore
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
@@ -38,31 +38,18 @@ import java.io.IOException
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
private val zxing = Zxing()
|
||||
private val decodingRunnable = Runnable {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
val result = decodeFrame()
|
||||
if (result != null) {
|
||||
cameraView.post { found(result) }
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var vibrator: Vibrator
|
||||
private lateinit var cameraView: CameraView
|
||||
private lateinit var zoomBar: SeekBar
|
||||
private lateinit var flashFab: View
|
||||
|
||||
private var decodingThread: Thread? = null
|
||||
private var preprocessor: Preprocessor? = null
|
||||
private var frameData: ByteArray? = null
|
||||
private var frameWidth: Int = 0
|
||||
private var frameHeight: Int = 0
|
||||
private var frameOrientation: Int = 0
|
||||
private var invert = false
|
||||
private var flash = false
|
||||
private var returnResult = false
|
||||
private var frontFacing = false
|
||||
private var fallbackBuffer: IntArray? = null
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
@@ -70,8 +57,9 @@ class CameraActivity : AppCompatActivity() {
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
REQUEST_CAMERA -> if (grantResults.size > 0 &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
REQUEST_CAMERA -> if (grantResults.isNotEmpty() &&
|
||||
grantResults[0] != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
R.string.no_camera_no_fun,
|
||||
@@ -94,14 +82,14 @@ class CameraActivity : AppCompatActivity() {
|
||||
cameraView = findViewById(R.id.camera_view) as CameraView
|
||||
zoomBar = findViewById(R.id.zoom) as SeekBar
|
||||
flashFab = findViewById(R.id.flash)
|
||||
flashFab.setOnClickListener { _ -> toggleTorchMode() }
|
||||
flashFab.setOnClickListener { toggleTorchMode() }
|
||||
|
||||
initCameraView()
|
||||
initZoomBar()
|
||||
restoreZoom()
|
||||
|
||||
if (intent?.action == Intent.ACTION_SEND) {
|
||||
if ("text/plain" == intent.type) {
|
||||
if (intent.type == "text/plain") {
|
||||
handleSendText(intent)
|
||||
} else if (intent.type?.startsWith("image/") == true) {
|
||||
handleSendImage(intent)
|
||||
@@ -112,16 +100,15 @@ class CameraActivity : AppCompatActivity() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
preprocessor?.destroy()
|
||||
fallbackBuffer = null
|
||||
saveZoom()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
System.gc()
|
||||
returnResult = "com.google.zxing.client.android.SCAN".equals(
|
||||
intent.action
|
||||
)
|
||||
if (hasCameraPermission()) {
|
||||
returnResult = "com.google.zxing.client.android.SCAN" == intent.action
|
||||
if (hasCameraPermission(this, REQUEST_CAMERA)) {
|
||||
openCamera()
|
||||
}
|
||||
}
|
||||
@@ -144,7 +131,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun closeCamera() {
|
||||
cancelDecoding()
|
||||
cameraView.close()
|
||||
}
|
||||
|
||||
@@ -204,18 +190,15 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun handleSendText(intent: Intent) {
|
||||
var text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (text?.isEmpty() == true) {
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (text?.isEmpty() == false) {
|
||||
startActivity(MainActivity.getEncodeIntent(this, text, true))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendImage(intent: Intent) {
|
||||
var uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
||||
if (uri == null) {
|
||||
return
|
||||
}
|
||||
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri ?: return
|
||||
val bitmap = try {
|
||||
MediaStore.Images.Media.getBitmap(contentResolver, uri)
|
||||
} catch (e: IOException) {
|
||||
@@ -229,7 +212,7 @@ class CameraActivity : AppCompatActivity() {
|
||||
).show()
|
||||
return
|
||||
}
|
||||
val result = zxing.decode(downsizeIfBigger(bitmap, 1024))
|
||||
val result = zxing.decodePositiveNegative(downsizeIfBigger(bitmap, 1024))
|
||||
if (result != null) {
|
||||
showResult(result)
|
||||
finish()
|
||||
@@ -243,21 +226,6 @@ class CameraActivity : AppCompatActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun hasCameraPermission(): Boolean {
|
||||
val permission = android.Manifest.permission.CAMERA
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, permission) !=
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this, arrayOf(permission),
|
||||
REQUEST_CAMERA
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun initCameraView() {
|
||||
cameraView.setUseOrientationListener(true)
|
||||
cameraView.setTapToFocus()
|
||||
@@ -265,21 +233,21 @@ class CameraActivity : AppCompatActivity() {
|
||||
override fun onConfigureParameters(
|
||||
parameters: Camera.Parameters
|
||||
) {
|
||||
if (parameters.isZoomSupported()) {
|
||||
val max = parameters.getMaxZoom()
|
||||
if (parameters.isZoomSupported) {
|
||||
val max = parameters.maxZoom
|
||||
if (zoomBar.max != max) {
|
||||
zoomBar.max = max
|
||||
zoomBar.progress = max / 10
|
||||
saveZoom()
|
||||
}
|
||||
parameters.setZoom(zoomBar.progress)
|
||||
parameters.zoom = zoomBar.progress
|
||||
} else {
|
||||
zoomBar.visibility = View.GONE
|
||||
}
|
||||
val sceneModes = parameters.supportedSceneModes
|
||||
sceneModes?.let {
|
||||
for (mode in sceneModes) {
|
||||
if (mode.equals(Camera.Parameters.SCENE_MODE_BARCODE)) {
|
||||
if (mode == Camera.Parameters.SCENE_MODE_BARCODE) {
|
||||
parameters.sceneMode = mode
|
||||
break
|
||||
}
|
||||
@@ -299,18 +267,33 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onCameraReady(camera: Camera) {
|
||||
frameWidth = cameraView.frameWidth
|
||||
frameHeight = cameraView.frameHeight
|
||||
frameOrientation = cameraView.frameOrientation
|
||||
camera.setPreviewCallback { data, _ -> frameData = data }
|
||||
val frameWidth = cameraView.frameWidth
|
||||
val frameHeight = cameraView.frameHeight
|
||||
val frameOrientation = cameraView.frameOrientation
|
||||
var decoding = true
|
||||
camera.setPreviewCallback { frameData, _ ->
|
||||
if (decoding) {
|
||||
val result = decodeFrame(
|
||||
frameData,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
result?.let {
|
||||
cameraView.post {
|
||||
vibrator.vibrate(100)
|
||||
showResult(result)
|
||||
}
|
||||
decoding = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreviewStarted(camera: Camera) {
|
||||
startDecoding()
|
||||
}
|
||||
|
||||
override fun onCameraStopping(camera: Camera) {
|
||||
cancelDecoding()
|
||||
camera.setPreviewCallback(null)
|
||||
}
|
||||
})
|
||||
@@ -334,12 +317,12 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setZoom(zoom: Int) {
|
||||
val camera: Camera? = cameraView.getCamera()
|
||||
val camera: Camera? = cameraView.camera
|
||||
camera?.let {
|
||||
try {
|
||||
val params = camera.getParameters()
|
||||
params.setZoom(zoom)
|
||||
camera.setParameters(params)
|
||||
val params = camera.parameters
|
||||
params.zoom = zoom
|
||||
camera.parameters = params
|
||||
} catch (e: RuntimeException) {
|
||||
// ignore; there's nothing we can do
|
||||
}
|
||||
@@ -370,18 +353,16 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun toggleTorchMode() {
|
||||
val camera = cameraView.getCamera()
|
||||
val parameters = camera?.getParameters()
|
||||
parameters?.setFlashMode(
|
||||
if (flash) {
|
||||
Camera.Parameters.FLASH_MODE_OFF
|
||||
} else {
|
||||
Camera.Parameters.FLASH_MODE_TORCH
|
||||
}
|
||||
)
|
||||
val camera = cameraView.camera
|
||||
val parameters = camera?.parameters
|
||||
parameters?.flashMode = if (flash) {
|
||||
Camera.Parameters.FLASH_MODE_OFF
|
||||
} else {
|
||||
Camera.Parameters.FLASH_MODE_TORCH
|
||||
}
|
||||
parameters?.let {
|
||||
try {
|
||||
camera.setParameters(parameters)
|
||||
camera.parameters = parameters
|
||||
flash = flash xor true
|
||||
} catch (e: RuntimeException) {
|
||||
// ignore; there's nothing we can do
|
||||
@@ -389,59 +370,39 @@ class CameraActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDecoding() {
|
||||
frameData = null
|
||||
decodingThread = Thread(decodingRunnable)
|
||||
decodingThread?.start()
|
||||
}
|
||||
|
||||
private fun cancelDecoding() {
|
||||
if (decodingThread != null) {
|
||||
decodingThread?.interrupt()
|
||||
try {
|
||||
decodingThread?.join()
|
||||
} catch (e: InterruptedException) {
|
||||
// parent thread was interrupted
|
||||
}
|
||||
decodingThread = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeFrame(): Result? {
|
||||
var fd = frameData
|
||||
fd ?: return null
|
||||
if (preprocessor == null) {
|
||||
preprocessor = Preprocessor(
|
||||
this,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
}
|
||||
var pp = preprocessor
|
||||
pp ?: return null
|
||||
pp.process(fd)
|
||||
private fun decodeFrame(
|
||||
frameData: ByteArray?,
|
||||
frameWidth: Int,
|
||||
frameHeight: Int,
|
||||
frameOrientation: Int
|
||||
): Result? {
|
||||
frameData ?: return null
|
||||
invert = invert xor true
|
||||
val pp = preprocessor ?: Preprocessor(
|
||||
this,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameOrientation
|
||||
)
|
||||
pp.process(frameData)
|
||||
preprocessor = pp
|
||||
return zxing.decode(
|
||||
fd,
|
||||
frameData,
|
||||
pp.outWidth,
|
||||
pp.outHeight,
|
||||
invert
|
||||
)
|
||||
}
|
||||
|
||||
private fun found(result: Result) {
|
||||
cancelDecoding()
|
||||
vibrator.vibrate(100)
|
||||
showResult(result)
|
||||
}
|
||||
|
||||
private fun showResult(result: Result) {
|
||||
val rawBytes = getRawBytes(result)
|
||||
|
||||
if (prefs.useHistory) {
|
||||
GlobalScope.launch {
|
||||
db.insertScan(
|
||||
System.currentTimeMillis(),
|
||||
result.text,
|
||||
rawBytes,
|
||||
result.barcodeFormat.toString()
|
||||
)
|
||||
}
|
||||
@@ -459,7 +420,8 @@ class CameraActivity : AppCompatActivity() {
|
||||
MainActivity.getDecodeIntent(
|
||||
this,
|
||||
result.text,
|
||||
result.barcodeFormat
|
||||
result.barcodeFormat,
|
||||
rawBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -478,12 +440,12 @@ fun downsizeIfBigger(bitmap: Bitmap, max: Int): Bitmap {
|
||||
if (bitmap.width > bitmap.height) {
|
||||
height = Math.round(
|
||||
width.toFloat() / bitmap.width.toFloat() *
|
||||
bitmap.height.toFloat()
|
||||
bitmap.height.toFloat()
|
||||
)
|
||||
} else {
|
||||
width = Math.round(
|
||||
height.toFloat() / bitmap.height.toFloat() *
|
||||
bitmap.width.toFloat()
|
||||
bitmap.width.toFloat()
|
||||
)
|
||||
}
|
||||
Bitmap.createScaledBitmap(bitmap, width, height, true)
|
||||
@@ -491,3 +453,16 @@ fun downsizeIfBigger(bitmap: Bitmap, max: Int): Bitmap {
|
||||
bitmap
|
||||
}
|
||||
}
|
||||
|
||||
fun getRawBytes(result: Result): ByteArray? {
|
||||
val metadata = result.resultMetadata ?: return null
|
||||
val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
|
||||
var bytes = ByteArray(0)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (seg in segments as Iterable<ByteArray>) {
|
||||
bytes += seg
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ package de.markusfisch.android.binaryeye.activity
|
||||
|
||||
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.Build
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
|
||||
@@ -36,32 +36,31 @@ class MainActivity : AppCompatActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (state == null) {
|
||||
var fragment: Fragment
|
||||
if (intent?.hasExtra(HISTORY) == true) {
|
||||
fragment = HistoryFragment()
|
||||
} else if (intent?.hasExtra(ENCODE) == true) {
|
||||
fragment = EncodeFragment.newInstance(
|
||||
intent.getStringExtra(ENCODE)
|
||||
)
|
||||
} else if (intent?.hasExtra(DECODE) == true) {
|
||||
fragment = DecodeFragment.newInstance(
|
||||
intent.getStringExtra(DECODE),
|
||||
intent.getSerializableExtra(
|
||||
DECODE_FORMAT
|
||||
) as BarcodeFormat
|
||||
)
|
||||
} else {
|
||||
fragment = DecodeFragment()
|
||||
}
|
||||
setFragment(supportFragmentManager, fragment)
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val HISTORY = "history"
|
||||
private const val ENCODE = "encode"
|
||||
private const val DECODE = "decode"
|
||||
private const val DECODE_FORMAT = "decode_format"
|
||||
private const val DECODED_TEXT = "decoded_text"
|
||||
private const val DECODED_FORMAT = "decoded_format"
|
||||
private const val DECODED_RAW = "decoded_raw"
|
||||
|
||||
fun getHistoryIntent(context: Context): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
@@ -77,10 +76,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(
|
||||
android.content.Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK or
|
||||
android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
Intent.FLAG_ACTIVITY_NO_HISTORY or
|
||||
flagActivityClearTask or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
)
|
||||
}
|
||||
return intent
|
||||
@@ -89,11 +91,15 @@ class MainActivity : AppCompatActivity() {
|
||||
fun getDecodeIntent(
|
||||
context: Context,
|
||||
text: String,
|
||||
format: BarcodeFormat
|
||||
format: BarcodeFormat,
|
||||
raw: ByteArray? = null
|
||||
): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.putExtra(DECODE, text)
|
||||
intent.putExtra(DECODE_FORMAT, format)
|
||||
intent.putExtra(DECODED_TEXT, text)
|
||||
intent.putExtra(DECODED_FORMAT, format)
|
||||
if (raw != null) {
|
||||
intent.putExtra(DECODED_RAW, raw)
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.markusfisch.android.binaryeye.adapter
|
||||
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.data.Database
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
@@ -33,22 +33,24 @@ class ScansAdapter(context: Context, cursor: Cursor) :
|
||||
cursor: Cursor
|
||||
) {
|
||||
val holder = getViewHolder(view)
|
||||
val content = cursor.getString(contentIndex)
|
||||
holder.timeView.text = cursor.getString(timeIndex)
|
||||
holder.contentView.text = cursor.getString(contentIndex)
|
||||
holder.contentView.text = if (content.isEmpty()) {
|
||||
context.getString(R.string.binary_data)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
holder.formatView.text = cursor.getString(formatIndex)
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
@@ -4,6 +4,7 @@ import de.markusfisch.android.binaryeye.data.Database
|
||||
import de.markusfisch.android.binaryeye.preference.Preferences
|
||||
|
||||
import android.app.Application
|
||||
import android.support.v8.renderscript.RenderScript
|
||||
|
||||
val db = Database()
|
||||
val prefs = Preferences()
|
||||
@@ -11,6 +12,11 @@ val prefs = Preferences()
|
||||
class BinaryEyeApp : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// required to make RenderScript work for Lineage 16.0
|
||||
// possibly because of a system/device bug
|
||||
RenderScript.forceCompat()
|
||||
|
||||
db.open(this)
|
||||
prefs.init(this)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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
|
||||
@@ -14,6 +15,7 @@ fun addFragment(fm: FragmentManager?, fragment: Fragment) {
|
||||
fm?.let { getTransaction(fm, fragment).addToBackStack(null).commit() }
|
||||
}
|
||||
|
||||
@SuppressLint("CommitTransaction")
|
||||
private fun getTransaction(
|
||||
fm: FragmentManager,
|
||||
fragment: Fragment
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
private val nonPrintable = Pattern.compile("[\\x00-\\x08\\x0e-\\x1f]")
|
||||
|
||||
fun hasNonPrintableCharacters(s: String) = nonPrintable.matcher(s).find()
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun hasWritePermission(activity: Activity, requestCode: Int = 1): Boolean {
|
||||
return hasPermission(
|
||||
activity,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
requestCode
|
||||
)
|
||||
}
|
||||
|
||||
fun hasPermission(
|
||||
activity: Activity,
|
||||
permission: String,
|
||||
requestCode: Int
|
||||
): Boolean {
|
||||
return if (ContextCompat.checkSelfPermission(activity, permission) !=
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
arrayOf(permission),
|
||||
requestCode
|
||||
)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.BuildConfig
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.content.FileProvider
|
||||
import android.widget.Toast
|
||||
|
||||
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
|
||||
execShareIntent(context, intent)
|
||||
}
|
||||
|
||||
fun shareUri(context: Context, uri: Uri, type: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.type = type
|
||||
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) {
|
||||
shareUri(context, getUriForFile(context, file), type)
|
||||
}
|
||||
|
||||
fun getUriForFile(context: Context, file: File): Uri {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(file)
|
||||
} else {
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".provider",
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,29 +2,33 @@ package de.markusfisch.android.binaryeye.app
|
||||
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Point
|
||||
import android.os.Build
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.view.ViewCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,69 +36,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 getStatusAndToolBarHeight(context: Context): Int {
|
||||
return getStatusBarHeight(context.resources) + getToolBarHeight(context)
|
||||
}
|
||||
|
||||
fun getStatusBarHeight(res: Resources): Int {
|
||||
return getIdentifierDimen(res, "status_bar_height")
|
||||
}
|
||||
|
||||
fun getToolBarHeight(context: Context): Int {
|
||||
val tv = TypedValue()
|
||||
return if (context.theme.resolveAttribute(
|
||||
android.R.attr.actionBarSize,
|
||||
tv,
|
||||
true
|
||||
))
|
||||
TypedValue.complexToDimensionPixelSize(
|
||||
tv.data,
|
||||
context.resources.displayMetrics
|
||||
)
|
||||
else
|
||||
0
|
||||
}
|
||||
|
||||
fun getNavigationBarSize(res: Resources): Point {
|
||||
val size = Point(0, 0)
|
||||
if (!getIdentifierBoolean(res, "config_showNavigationBar")) {
|
||||
return size
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
|
||||
val conf = res.getConfiguration()
|
||||
if (conf.orientation == Configuration.ORIENTATION_LANDSCAPE &&
|
||||
// according to https://developer.android.com/training/multiscreen/screensizes.html#TaskUseSWQuali
|
||||
// only a screen < 600 dp is considered to be a phone
|
||||
// and can move its navigation bar to the side
|
||||
conf.smallestScreenWidthDp < 600) {
|
||||
size.x = getIdentifierDimen(
|
||||
res,
|
||||
"navigation_bar_height_landscape"
|
||||
)
|
||||
return size
|
||||
}
|
||||
}
|
||||
size.y = getIdentifierDimen(res, "navigation_bar_height")
|
||||
return size
|
||||
}
|
||||
|
||||
private fun getIdentifierBoolean(res: Resources, name: String): Boolean {
|
||||
val id = res.getIdentifier(name, "bool", "android")
|
||||
return id > 0 && res.getBoolean(id)
|
||||
}
|
||||
|
||||
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,7 +1,10 @@
|
||||
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
|
||||
@@ -13,7 +16,7 @@ class Database {
|
||||
db = OpenHelper(context).writableDatabase
|
||||
}
|
||||
|
||||
fun getScans() = db.rawQuery(
|
||||
fun getScans(): Cursor? = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_ID,
|
||||
$SCANS_DATETIME,
|
||||
@@ -24,17 +27,23 @@ class Database {
|
||||
""", null
|
||||
)
|
||||
|
||||
fun getScan(id: Long) = db.rawQuery(
|
||||
fun getScan(id: Long): Cursor? = db.rawQuery(
|
||||
"""SELECT
|
||||
$SCANS_DATETIME,
|
||||
$SCANS_CONTENT,
|
||||
$SCANS_RAW,
|
||||
$SCANS_FORMAT
|
||||
FROM $SCANS
|
||||
WHERE $SCANS_ID = ?
|
||||
""", arrayOf("$id")
|
||||
)
|
||||
|
||||
fun insertScan(timestamp: Long, name: String, code: String): Long {
|
||||
fun insertScan(
|
||||
timestamp: Long,
|
||||
content: String,
|
||||
raw: ByteArray?,
|
||||
format: String
|
||||
): Long {
|
||||
val cv = ContentValues()
|
||||
cv.put(
|
||||
SCANS_DATETIME, DateFormat.format(
|
||||
@@ -42,8 +51,13 @@ class Database {
|
||||
timestamp
|
||||
).toString()
|
||||
)
|
||||
cv.put(SCANS_CONTENT, name)
|
||||
cv.put(SCANS_FORMAT, code)
|
||||
if (hasNonPrintableCharacters(content) && raw != null) {
|
||||
cv.put(SCANS_CONTENT, "")
|
||||
cv.put(SCANS_RAW, raw)
|
||||
} else {
|
||||
cv.put(SCANS_CONTENT, content)
|
||||
}
|
||||
cv.put(SCANS_FORMAT, format)
|
||||
return db.insert(SCANS, null, cv)
|
||||
}
|
||||
|
||||
@@ -56,7 +70,7 @@ class Database {
|
||||
}
|
||||
|
||||
private class OpenHelper(context: Context) :
|
||||
SQLiteOpenHelper(context, "history.db", null, 1) {
|
||||
SQLiteOpenHelper(context, "history.db", null, 2) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
createScans(db)
|
||||
}
|
||||
@@ -66,6 +80,9 @@ class Database {
|
||||
oldVersion: Int,
|
||||
newVersion: Int
|
||||
) {
|
||||
if (oldVersion < 2) {
|
||||
addRawColumn(db)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +91,7 @@ class Database {
|
||||
const val SCANS_ID = "_id"
|
||||
const val SCANS_DATETIME = "_datetime"
|
||||
const val SCANS_CONTENT = "content"
|
||||
const val SCANS_RAW = "raw"
|
||||
const val SCANS_FORMAT = "format"
|
||||
|
||||
private fun createScans(db: SQLiteDatabase) {
|
||||
@@ -83,9 +101,14 @@ class Database {
|
||||
$SCANS_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
$SCANS_DATETIME DATETIME NOT NULL,
|
||||
$SCANS_CONTENT TEXT NOT NULL,
|
||||
$SCANS_RAW BLOB,
|
||||
$SCANS_FORMAT TEXT NOT NULL
|
||||
)"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun addRawColumn(db: SQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE $SCANS ADD COLUMN $SCANS_RAW BLOB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,13 @@ 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.BuildConfig
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.content.FileProvider
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -41,30 +37,38 @@ class BarcodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
val args = getArguments()
|
||||
val args = arguments
|
||||
args?.let {
|
||||
val size = args.getInt(SIZE)
|
||||
val bitmap: Bitmap?
|
||||
try {
|
||||
bitmap = Zxing.encodeAsBitmap(
|
||||
args.getString(CONTENT),
|
||||
args.getSerializable(FORMAT) as BarcodeFormat,
|
||||
size,
|
||||
size
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
e.message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
fragmentManager.popBackStack()
|
||||
return null
|
||||
}
|
||||
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
|
||||
view.findViewById<View>(R.id.share).setOnClickListener { _ ->
|
||||
bitmap?.let {
|
||||
share(bitmap)
|
||||
val content = args.getString(CONTENT)
|
||||
val format = args.getSerializable(FORMAT) as BarcodeFormat?
|
||||
if (content != null && format != null) {
|
||||
val size = args.getInt(SIZE)
|
||||
val bitmap: Bitmap?
|
||||
try {
|
||||
bitmap = Zxing.encodeAsBitmap(
|
||||
content,
|
||||
format,
|
||||
size,
|
||||
size
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
var message = e.message
|
||||
if (message == null || message.isEmpty()) {
|
||||
message = getString(R.string.error_encoding_barcode)
|
||||
}
|
||||
Toast.makeText(
|
||||
activity,
|
||||
message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
fragmentManager.popBackStack()
|
||||
return null
|
||||
}
|
||||
view.findViewById<ImageView>(R.id.barcode).setImageBitmap(bitmap)
|
||||
view.findViewById<View>(R.id.share).setOnClickListener {
|
||||
bitmap?.let {
|
||||
share(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,40 +81,24 @@ class BarcodeFragment : Fragment() {
|
||||
val file = saveBitmap(bitmap)
|
||||
GlobalScope.launch(Main) {
|
||||
file?.let {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_STREAM, getUriForFile(file))
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.setType("image/png")
|
||||
startActivity(intent)
|
||||
shareFile(context, file, "image/png")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveBitmap(bitmap: Bitmap): File? {
|
||||
try {
|
||||
return try {
|
||||
val file = File(
|
||||
context.getExternalCacheDir(),
|
||||
context.externalCacheDir,
|
||||
"shared_barcode.png"
|
||||
)
|
||||
val fos = FileOutputStream(file)
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)
|
||||
fos.close()
|
||||
return file
|
||||
file
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUriForFile(file: File): Uri {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Uri.fromFile(file)
|
||||
} else {
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".provider",
|
||||
file
|
||||
)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +117,7 @@ class BarcodeFragment : Fragment() {
|
||||
args.putSerializable(FORMAT, format)
|
||||
args.putInt(SIZE, size)
|
||||
val fragment = BarcodeFragment()
|
||||
fragment.setArguments(args)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,25 @@ 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 de.markusfisch.android.binaryeye.actions.IAction
|
||||
import de.markusfisch.android.binaryeye.actions.validateOrGetNew
|
||||
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 android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
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
|
||||
@@ -18,11 +28,25 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URLEncoder
|
||||
|
||||
class DecodeFragment : Fragment() {
|
||||
private lateinit var contentView: EditText
|
||||
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)
|
||||
@@ -42,37 +66,100 @@ class DecodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
val content = arguments?.getString(CONTENT) ?: ""
|
||||
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<EditText>(R.id.content)
|
||||
contentView.setText(content)
|
||||
contentView = view.findViewById(R.id.content)
|
||||
val shareFab = view.findViewById<ImageView>(R.id.share)
|
||||
|
||||
view.findViewById<View>(R.id.share).setOnClickListener { _ ->
|
||||
share(getContent())
|
||||
if (!isBinary) {
|
||||
contentView.setText(inputContent)
|
||||
contentView.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
updateViewsAndAction(content.toByteArray())
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
count: Int,
|
||||
after: Int
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(
|
||||
s: CharSequence?,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {
|
||||
}
|
||||
})
|
||||
shareFab.setOnClickListener { v ->
|
||||
shareText(v.context, content)
|
||||
}
|
||||
} else {
|
||||
contentView.setText(R.string.binary_data)
|
||||
contentView.isEnabled = false
|
||||
shareFab.setImageResource(R.drawable.ic_action_save)
|
||||
shareFab.setOnClickListener {
|
||||
askForFileNameAndSave(raw)
|
||||
}
|
||||
}
|
||||
|
||||
formatView = view.findViewById(R.id.format)
|
||||
hexView = view.findViewById(R.id.hex)
|
||||
|
||||
updateViewsAndAction(raw)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun updateViewsAndAction(bytes: ByteArray) {
|
||||
action = action.validateOrGetNew(bytes)
|
||||
formatView.text = resources.getQuantityString(
|
||||
R.plurals.barcode_info,
|
||||
bytes.size,
|
||||
format.toString(),
|
||||
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
|
||||
menu.findItem(R.id.create).isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -80,17 +167,13 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContent(): String {
|
||||
return contentView.getText().toString()
|
||||
}
|
||||
|
||||
private fun copyToClipboard(text: String) {
|
||||
activity ?: return
|
||||
|
||||
val cm = activity.getSystemService(
|
||||
Context.CLIPBOARD_SERVICE
|
||||
) as ClipboardManager
|
||||
cm.setText(text)
|
||||
cm.text = text
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.put_into_clipboard,
|
||||
@@ -98,18 +181,28 @@ class DecodeFragment : Fragment() {
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
private fun openUrl(
|
||||
url: String,
|
||||
executeCustomAction: Boolean = true,
|
||||
searchIfNoUrl: Boolean = true
|
||||
) {
|
||||
if (activity == null || url.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(url).normalizeScheme()
|
||||
)
|
||||
if (intent.resolveActivity(activity.getPackageManager()) != null) {
|
||||
startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
var uri = Uri.parse(url)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
uri = uri.normalizeScheme()
|
||||
}
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
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
|
||||
@@ -117,24 +210,123 @@ class DecodeFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun share(text: String) {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text)
|
||||
intent.setType("text/plain")
|
||||
startActivity(intent)
|
||||
private fun pickSearchEngineAndSearch(context: Context, query: String) {
|
||||
val urls = context.resources.getStringArray(
|
||||
R.array.search_engines_values
|
||||
)
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.pick_search_engine)
|
||||
.setItems(R.array.search_engines_names) { _, which ->
|
||||
openUrl(
|
||||
urls[which] + URLEncoder.encode(query, "utf-8"),
|
||||
executeCustomAction = false,
|
||||
searchIfNoUrl = false
|
||||
)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun askForFileNameAndSave(raw: ByteArray) {
|
||||
val ac = activity ?: return
|
||||
val view = ac.layoutInflater.inflate(R.layout.dialog_save_file, null)
|
||||
val editText = view.findViewById<EditText>(R.id.file_name)
|
||||
AlertDialog.Builder(ac)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (hasWritePermission(ac)) {
|
||||
val messageId = saveByteArray(
|
||||
editText.text.toString(),
|
||||
raw
|
||||
)
|
||||
if (messageId > 0) {
|
||||
Toast.makeText(
|
||||
ac, messageId,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONTENT = "content"
|
||||
private const val FORMAT = "format"
|
||||
private const val RAW = "raw"
|
||||
|
||||
fun newInstance(content: String, format: BarcodeFormat): Fragment {
|
||||
fun newInstance(
|
||||
content: String,
|
||||
format: BarcodeFormat,
|
||||
raw: ByteArray? = null
|
||||
): Fragment {
|
||||
val args = Bundle()
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
if (raw != null) {
|
||||
args.putByteArray(RAW, raw)
|
||||
}
|
||||
val fragment = DecodeFragment()
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hexDump(bytes: ByteArray, charsPerLine: Int): String {
|
||||
if (charsPerLine < 4 || bytes.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val dump = StringBuilder()
|
||||
val hex = StringBuilder()
|
||||
val ascii = StringBuilder()
|
||||
val itemsPerLine = (charsPerLine - 1) / 4
|
||||
val len = bytes.size
|
||||
var i = 0
|
||||
while (true) {
|
||||
val ord = bytes[i]
|
||||
hex.append(String.format("%02X ", ord))
|
||||
ascii.append(if (ord > 31) ord.toChar() else " ")
|
||||
|
||||
++i
|
||||
|
||||
val posInLine = i % itemsPerLine
|
||||
val atEnd = i >= len
|
||||
var atLineEnd = posInLine == 0
|
||||
if (atEnd && !atLineEnd) {
|
||||
for (j in posInLine until itemsPerLine) {
|
||||
hex.append(" ")
|
||||
}
|
||||
atLineEnd = true
|
||||
}
|
||||
if (atLineEnd) {
|
||||
dump.append(hex.toString())
|
||||
dump.append(" ")
|
||||
dump.append(ascii.toString())
|
||||
dump.append("\n")
|
||||
hex.setLength(0)
|
||||
ascii.setLength(0)
|
||||
}
|
||||
if (atEnd) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return dump.toString()
|
||||
}
|
||||
|
||||
fun saveByteArray(name: String, raw: ByteArray): Int {
|
||||
return try {
|
||||
val file = File(
|
||||
Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
),
|
||||
name
|
||||
)
|
||||
if (file.exists()) {
|
||||
return R.string.error_file_exists
|
||||
}
|
||||
file.writeBytes(raw)
|
||||
0
|
||||
} catch (e: IOException) {
|
||||
R.string.error_saving_binary_data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,22 +50,20 @@ class EncodeFragment : Fragment() {
|
||||
false
|
||||
)
|
||||
|
||||
formatView = view.findViewById<Spinner>(R.id.format)
|
||||
formatView.setAdapter(
|
||||
ArrayAdapter<String>(
|
||||
activity,
|
||||
android.R.layout.simple_list_item_1,
|
||||
writers.map { it -> it.name }
|
||||
)
|
||||
formatView = view.findViewById(R.id.format)
|
||||
formatView.adapter = ArrayAdapter<String>(
|
||||
activity,
|
||||
android.R.layout.simple_list_item_1,
|
||||
writers.map { it.name }
|
||||
)
|
||||
|
||||
sizeView = view.findViewById<TextView>(R.id.size_display)
|
||||
sizeBarView = view.findViewById<SeekBar>(R.id.size_bar)
|
||||
sizeView = view.findViewById(R.id.size_display)
|
||||
sizeBarView = view.findViewById(R.id.size_bar)
|
||||
initSizeBar()
|
||||
|
||||
val contentView = view.findViewById<EditText>(R.id.content)
|
||||
|
||||
val args = getArguments()
|
||||
val args = arguments
|
||||
args?.let {
|
||||
contentView.setText(args.getString(CONTENT))
|
||||
formatView.setSelection(
|
||||
@@ -76,9 +74,9 @@ class EncodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.encode).setOnClickListener { v ->
|
||||
val format = writers.get(formatView.getSelectedItemPosition())
|
||||
var size = getSize(sizeBarView.getProgress())
|
||||
val content = contentView.getText().toString()
|
||||
val format = writers[formatView.selectedItemPosition]
|
||||
val size = getSize(sizeBarView.progress)
|
||||
val content = contentView.text.toString()
|
||||
if (content.isEmpty()) {
|
||||
Toast.makeText(
|
||||
v.context,
|
||||
@@ -101,7 +99,7 @@ class EncodeFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun initSizeBar() {
|
||||
updateSize(sizeBarView.getProgress())
|
||||
updateSize(sizeBarView.progress)
|
||||
sizeBarView.setOnSeekBarChangeListener(
|
||||
object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(
|
||||
@@ -120,7 +118,7 @@ class EncodeFragment : Fragment() {
|
||||
|
||||
private fun updateSize(power: Int) {
|
||||
val size = getSize(power)
|
||||
sizeView.setText("${size}x$size")
|
||||
sizeView.text = getString(R.string.width_by_height, size, size)
|
||||
}
|
||||
|
||||
private fun getSize(power: Int) = 128 * (power + 1)
|
||||
@@ -130,7 +128,7 @@ class EncodeFragment : Fragment() {
|
||||
Context.INPUT_METHOD_SERVICE
|
||||
) as InputMethodManager?
|
||||
im?.let {
|
||||
im.hideSoftInputFromWindow(view.getWindowToken(), 0)
|
||||
im.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +144,7 @@ class EncodeFragment : Fragment() {
|
||||
args.putString(CONTENT, content)
|
||||
args.putSerializable(FORMAT, format)
|
||||
val fragment = EncodeFragment()
|
||||
fragment.setArguments(args)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package de.markusfisch.android.binaryeye.fragment
|
||||
import com.google.zxing.BarcodeFormat
|
||||
|
||||
import de.markusfisch.android.binaryeye.adapter.ScansAdapter
|
||||
import de.markusfisch.android.binaryeye.app.db
|
||||
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.fragment.DecodeFragment
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
|
||||
import android.app.AlertDialog
|
||||
@@ -16,6 +16,9 @@ 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.widget.ListView
|
||||
@@ -27,6 +30,12 @@ import kotlinx.coroutines.launch
|
||||
class HistoryFragment : Fragment() {
|
||||
private lateinit var listView: ListView
|
||||
private lateinit var fab: View
|
||||
private lateinit var progressView: View
|
||||
|
||||
override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -56,11 +65,13 @@ class HistoryFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
|
||||
fab = view.findViewById(R.id.clear)
|
||||
fab = view.findViewById(R.id.share)
|
||||
fab.setOnClickListener { v ->
|
||||
askToRemoveAllScans(v.context)
|
||||
pickListSeparatorAndShare(v.context)
|
||||
}
|
||||
|
||||
progressView = view.findViewById(R.id.progress_view)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -74,6 +85,20 @@ class HistoryFragment : Fragment() {
|
||||
update(context)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.fragment_history, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.clear -> {
|
||||
askToRemoveAllScans(context)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initHistorySwitch(switchView: SwitchCompat) {
|
||||
switchView.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.useHistory = isChecked
|
||||
@@ -87,17 +112,19 @@ class HistoryFragment : Fragment() {
|
||||
GlobalScope.launch {
|
||||
val cursor = db.getScans()
|
||||
GlobalScope.launch(Main) {
|
||||
fab.visibility = if (cursor != null && cursor.getCount() > 0) {
|
||||
fab.visibility = if (cursor != null && cursor.count > 0) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
if (listView.adapter == null) {
|
||||
listView.adapter = ScansAdapter(context, cursor)
|
||||
} else {
|
||||
val adapter = listView.adapter as ScansAdapter
|
||||
adapter.changeCursor(cursor)
|
||||
adapter.notifyDataSetChanged()
|
||||
cursor?.let {
|
||||
if (listView.adapter == null) {
|
||||
listView.adapter = ScansAdapter(context, cursor)
|
||||
} else {
|
||||
val adapter = listView.adapter as ScansAdapter
|
||||
adapter.changeCursor(cursor)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,13 +134,15 @@ class HistoryFragment : Fragment() {
|
||||
val cursor = db.getScan(id)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val contentIndex = cursor.getColumnIndex(Database.SCANS_CONTENT)
|
||||
val rawIndex = cursor.getColumnIndex(Database.SCANS_RAW)
|
||||
val formatIndex = cursor.getColumnIndex(Database.SCANS_FORMAT)
|
||||
try {
|
||||
addFragment(
|
||||
fragmentManager,
|
||||
DecodeFragment.newInstance(
|
||||
cursor.getString(contentIndex),
|
||||
BarcodeFormat.valueOf(cursor.getString(formatIndex))
|
||||
BarcodeFormat.valueOf(cursor.getString(formatIndex)),
|
||||
cursor.getBlob(rawIndex)
|
||||
)
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
@@ -146,4 +175,46 @@ class HistoryFragment : Fragment() {
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun pickListSeparatorAndShare(context: Context) {
|
||||
val separators = context.resources.getStringArray(
|
||||
R.array.list_separators_values
|
||||
)
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.pick_list_separator)
|
||||
.setItems(R.array.list_separators_names) { _, which ->
|
||||
shareScans(separators[which])
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun shareScans(separator: String) {
|
||||
if (progressView.visibility == View.VISIBLE) {
|
||||
return
|
||||
}
|
||||
progressView.visibility = View.VISIBLE
|
||||
GlobalScope.launch {
|
||||
val cursor = db.getScans()
|
||||
cursor?.let {
|
||||
val sb = StringBuilder()
|
||||
if (cursor.moveToFirst()) {
|
||||
val contentIndex = cursor.getColumnIndex(
|
||||
Database.SCANS_CONTENT
|
||||
)
|
||||
do {
|
||||
sb.append(cursor.getString(contentIndex))
|
||||
sb.append(separator)
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
cursor.close()
|
||||
val text = sb.toString()
|
||||
if (text.isNotEmpty()) {
|
||||
GlobalScope.launch(Main) {
|
||||
progressView.visibility = View.GONE
|
||||
shareText(context, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class Preprocessor(
|
||||
context: Context,
|
||||
width: Int,
|
||||
height: Int,
|
||||
val orientation: Int
|
||||
private val orientation: Int
|
||||
) {
|
||||
var outWidth = 0
|
||||
var outHeight = 0
|
||||
@@ -42,7 +42,7 @@ class Preprocessor(
|
||||
Allocation.USAGE_SCRIPT
|
||||
)
|
||||
|
||||
var f = .75f
|
||||
val f = .75f
|
||||
var w = Math.round(width * f)
|
||||
var h = Math.round(height * f)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class Zxing {
|
||||
)
|
||||
|
||||
val hints = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java)
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats)
|
||||
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
|
||||
|
||||
multiFormatReader.setHints(hints)
|
||||
}
|
||||
@@ -71,6 +71,42 @@ class Zxing {
|
||||
height,
|
||||
false
|
||||
)
|
||||
return decodeLuminanceSource(source, invert)
|
||||
}
|
||||
|
||||
fun decodePositiveNegative(bitmap: Bitmap): Result? {
|
||||
val result = decode(bitmap, false)
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
return decode(bitmap, true)
|
||||
}
|
||||
|
||||
fun decode(bitmap: Bitmap, invert: Boolean = false): Result? {
|
||||
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||
return decode(pixels, bitmap, invert)
|
||||
}
|
||||
|
||||
fun decode(
|
||||
pixels: IntArray,
|
||||
bitmap: Bitmap,
|
||||
invert: Boolean = false
|
||||
): Result? {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
if (bitmap.config != Bitmap.Config.ARGB_8888) {
|
||||
bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
} else {
|
||||
bitmap
|
||||
}.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
val source = RGBLuminanceSource(width, height, pixels)
|
||||
return decodeLuminanceSource(source, invert)
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(
|
||||
source: LuminanceSource,
|
||||
invert: Boolean
|
||||
): Result? {
|
||||
return decodeLuminanceSource(
|
||||
if (invert) {
|
||||
source.invert()
|
||||
@@ -80,23 +116,6 @@ class Zxing {
|
||||
)
|
||||
}
|
||||
|
||||
fun decode(bitmap: Bitmap): Result? {
|
||||
val width = bitmap.getWidth()
|
||||
val height = bitmap.getHeight()
|
||||
val pixels = IntArray(width * height)
|
||||
if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
|
||||
bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
} else {
|
||||
bitmap
|
||||
}.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
val source = RGBLuminanceSource(width, height, pixels)
|
||||
val result = decodeLuminanceSource(source)
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
return decodeLuminanceSource(source.invert())
|
||||
}
|
||||
|
||||
private fun decodeLuminanceSource(source: LuminanceSource): Result? {
|
||||
val bitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
return try {
|
||||
@@ -119,24 +138,28 @@ class Zxing {
|
||||
height: Int
|
||||
): Bitmap? {
|
||||
val result = MultiFormatWriter().encode(
|
||||
text, format,
|
||||
width, height, null
|
||||
text,
|
||||
format,
|
||||
width,
|
||||
height,
|
||||
null
|
||||
)
|
||||
val w = result.getWidth()
|
||||
val h = result.getHeight()
|
||||
val w = result.width
|
||||
val h = result.height
|
||||
val pixels = IntArray(w * h)
|
||||
var offset = 0
|
||||
for (y in 0..h - 1) {
|
||||
for (x in 0..w - 1) {
|
||||
pixels[offset + x] = if (result.get(x, y))
|
||||
for (y in 0 until h) {
|
||||
for (x in 0 until w) {
|
||||
pixels[offset + x] = if (result.get(x, y)) {
|
||||
BLACK
|
||||
else
|
||||
} else {
|
||||
WHITE
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="line"
|
||||
android:gravity="center">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/accent"/>
|
||||
</shape>
|
||||
@@ -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,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="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</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>
|
||||
@@ -10,8 +10,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="@drawable/background_scanner">
|
||||
android:fitsSystemWindows="true">
|
||||
<include layout="@layout/toolbar"/>
|
||||
<SeekBar
|
||||
android:id="@+id/zoom"
|
||||
@@ -33,6 +32,7 @@
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="20dp"
|
||||
android:contentDescription="@string/toggle_flash"
|
||||
android:src="@drawable/ic_action_flash"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/save_as_file_name"/>
|
||||
<EditText
|
||||
android:id="@+id/file_name"
|
||||
android:inputType="text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/file_name" />
|
||||
</LinearLayout>
|
||||
@@ -4,8 +4,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<de.markusfisch.android.scalingimageview.widget.ScalingImageView
|
||||
android:id="@+id/barcode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -19,6 +18,7 @@
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="20dp"
|
||||
android:contentDescription="@string/share"
|
||||
android:src="@drawable/ic_action_share"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -4,16 +4,52 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<EditText
|
||||
android:id="@+id/content"
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:gravity="start|top"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textMultiLine"
|
||||
android:typeface="monospace"
|
||||
android:hint="@string/content"/>
|
||||
android:layout_height="match_parent">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
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"
|
||||
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_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="end|top"
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/hex"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/format"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:typeface="monospace"
|
||||
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
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:id="@+id/share"
|
||||
@@ -23,6 +59,7 @@
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="20dp"
|
||||
android:contentDescription="@string/share"
|
||||
android:src="@drawable/ic_action_share"
|
||||
app:elevation="4dp"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/content"/>
|
||||
<EditText
|
||||
tools:targetApi="o"
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -51,7 +52,8 @@
|
||||
android:typeface="monospace"
|
||||
android:imeOptions="flagNoExtractUi"
|
||||
android:inputType="textMultiLine"
|
||||
android:hint="@string/input_content_here"/>
|
||||
android:hint="@string/input_content_here"
|
||||
android:importantForAutofill="no"/>
|
||||
<Button
|
||||
style="@style/Button"
|
||||
android:id="@+id/encode"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:choiceMode="singleChoice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@android:color/transparent"
|
||||
android:divider="@color/separator"
|
||||
android:dividerHeight="1px"/>
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:visibility="gone"
|
||||
@@ -22,13 +22,15 @@
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
tools:ignore="UnusedAttribute"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/clear"
|
||||
android:id="@+id/share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="20dp"
|
||||
android:src="@drawable/ic_action_clear"
|
||||
android:contentDescription="@string/share"
|
||||
android:src="@drawable/ic_action_share"
|
||||
app:elevation="4dp"/>
|
||||
<include layout="@layout/progress_view"/>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/progress_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_progress"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:visibility="gone">
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"/>
|
||||
</FrameLayout>
|
||||
@@ -15,6 +15,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:layout_toRightOf="@+id/time"
|
||||
android:layout_toEndOf="@+id/time"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:material="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/remove_scan"
|
||||
android:title="@string/remove_scan"
|
||||
android:icon="@drawable/ic_action_remove"
|
||||
android:id="@+id/clear"
|
||||
android:title="@string/clear_history"
|
||||
android:icon="@drawable/ic_action_clear"
|
||||
material:showAsAction="ifRoom"/>
|
||||
</menu>
|
||||
@@ -5,22 +5,52 @@
|
||||
<string name="compose_barcode">Barcode erstellen</string>
|
||||
<string name="decode_barcode">Barcode dekodieren</string>
|
||||
<string name="content">Inhalt</string>
|
||||
<string name="binary_data">(Binäre Daten)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d Zeichen</item>
|
||||
<item quantity="other">%1$s\n%2$d Zeichen</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Licht an/ausschalten</string>
|
||||
<string name="share">Teilen</string>
|
||||
<string name="copy_to_clipboard">In die Zwischenablage kopieren</string>
|
||||
<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="width_by_height">%1$d×%2$d</string>
|
||||
<string name="input_content_here">Gib den Inhalt hier ein</string>
|
||||
<string name="encode">KODIEREN</string>
|
||||
<string name="error_no_content">Fehlender Inhalt</string>
|
||||
<string name="error_encoding_barcode">Barcode kann nicht generiert werden</string>
|
||||
<string name="view_barcode">Barcode ansehen</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Kamera wechseln</string>
|
||||
<string name="history">Gespeicherte Codes</string>
|
||||
<string name="use_history">Codes speichern</string>
|
||||
<string name="remove_scan">Code entfernen</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>
|
||||
<string name="no_barcode_found">Kein Barcode gefunden</string>
|
||||
<string name="pick_list_separator">Wie sollen die Einträge getrennt werden?</string>
|
||||
<string name="separator_line_break">Zeilenumbruch</string>
|
||||
<string name="separator_ruler">Trennstrich</string>
|
||||
<string name="save_as_file_name">Speichern als?</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Cette application n\'a aucun sens sans accès possible à la caméra. Au revoir.</string>
|
||||
<string name="camera_error">Impossible d\'accéder à la caméra, veuillez réessayer</string>
|
||||
<string name="scan_code">Scanner le code</string>
|
||||
<string name="compose_barcode">Créer un code-barres</string>
|
||||
<string name="decode_barcode">Décoder le code-barres</string>
|
||||
<string name="content">Contenu</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="copy_to_clipboard">Copier dans le presse-papier</string>
|
||||
<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>
|
||||
<string name="input_content_here">Entrez votre contenu ici</string>
|
||||
<string name="encode">ENCODER</string>
|
||||
<string name="error_no_content">Contenu manquant</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">Voir le code barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Changer de camera</string>
|
||||
<string name="history">Historique</string>
|
||||
<string name="use_history">Sauvegarder l\'histoire des scans</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>
|
||||
<string name="no_barcode_found">Aucun code-barres trouvé</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="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>
|
||||
</resources>
|
||||
@@ -3,24 +3,54 @@
|
||||
<string name="camera_error">Nem lehet hozzáférni a kamerához, próbálja meg újra</string>
|
||||
<string name="scan_code">Kód beolvasása</string>
|
||||
<string name="compose_barcode">Vonalkód létrehozása</string>
|
||||
<string name="decode_barcode">Decode barcode</string>
|
||||
<string name="decode_barcode">Vonalkód visszafejtése</string>
|
||||
<string name="content">Tartalom</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>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Vaku be- vagy kikapcsolása</string>
|
||||
<string name="share">Megosztás</string>
|
||||
<string name="copy_to_clipboard">Másolás a vágólapra</string>
|
||||
<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">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">History</string>
|
||||
<string name="use_history">Save scan history</string>
|
||||
<string name="remove_scan">Remove scan</string>
|
||||
<string name="really_remove_scan">Really remove scan?</string>
|
||||
<string name="really_remove_all_scans">Really remove all scans?</string>
|
||||
<string name="no_barcode_found">No barcode found</string>
|
||||
<string name="history">Előzmények</string>
|
||||
<string name="use_history">Beolvasási előzmények mentése</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">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>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="no_camera_no_fun">Aplikasi ini 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" tools:ignore="UnusedQuantity">
|
||||
<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="use_history">Simpan riwayat pemindaian</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">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>
|
||||
</resources>
|
||||
@@ -0,0 +1,56 @@
|
||||
<resources>
|
||||
<string name="no_camera_no_fun">Quest\'app non ha alcuna utilità senza accesso alla camera. Addio.</string>
|
||||
<string name="camera_error">Impossibile accedere alla camera. Riprovare</string>
|
||||
<string name="scan_code">Codice</string>
|
||||
<string name="compose_barcode">Crea codice a barre</string>
|
||||
<string name="decode_barcode">Decodifica codice a barre</string>
|
||||
<string name="content">Contenuto</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Flash</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="copy_to_clipboard">Copia negli appunti</string>
|
||||
<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>
|
||||
<string name="input_content_here">Contenuto input qui</string>
|
||||
<string name="encode">CODIFICA</string>
|
||||
<string name="error_no_content">Contenuto mancante</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">Vedi codice a barre</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Cambia camera</string>
|
||||
<string name="history">Cronologia</string>
|
||||
<string name="use_history">Salva cronologia delle scansioni</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>
|
||||
<string name="no_barcode_found">Nessun codice a barre trovato</string>
|
||||
<string name="pick_list_separator">Come separare elementi di una lista?</string>
|
||||
<string name="separator_line_break">Interruzione di linea</string>
|
||||
<string name="separator_ruler">Righello</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="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>
|
||||
</resources>
|
||||
@@ -5,22 +5,52 @@
|
||||
<string name="compose_barcode">Scan een barcode</string>
|
||||
<string name="decode_barcode">Decode barcode</string>
|
||||
<string name="content">Inhoud</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="copy_to_clipboard">Kopiëren naar klembord</string>
|
||||
<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>
|
||||
<string name="input_content_here">Voer hier de inhoud in</string>
|
||||
<string name="encode">CODEREN</string>
|
||||
<string name="error_no_content">Inhoud ontbreekt</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">Barcode bekijken</string>
|
||||
<string name="info">Informatie</string>
|
||||
<string name="switch_camera">Camera wisselen</string>
|
||||
<string name="history">History</string>
|
||||
<string name="use_history">Save scan history</string>
|
||||
<string name="remove_scan">Remove scan</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>
|
||||
<string name="no_barcode_found">No barcode found</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="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>
|
||||
</resources>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<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>
|
||||
<color name="background_progress">#8000</color>
|
||||
<color name="button_text">#fff</color>
|
||||
<color name="separator">#333</color>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<string-array name="list_separators_names">
|
||||
<item>@string/separator_line_break</item>
|
||||
<item>@string/separator_ruler</item>
|
||||
</string-array>
|
||||
<string-array name="list_separators_values">
|
||||
<item>\n</item>
|
||||
<item>\n---\n</item>
|
||||
</string-array>
|
||||
</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>
|
||||
@@ -5,22 +5,52 @@
|
||||
<string name="compose_barcode">Compose barcode</string>
|
||||
<string name="decode_barcode">Decode barcode</string>
|
||||
<string name="content">Content</string>
|
||||
<string name="binary_data">(binary data)</string>
|
||||
<plurals name="barcode_info">
|
||||
<item quantity="one">%1$s\n%2$d character</item>
|
||||
<item quantity="other">%1$s\n%2$d characters</item>
|
||||
</plurals>
|
||||
<string name="toggle_flash">Toggle flash</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="copy_to_clipboard">Copy to clipboard</string>
|
||||
<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>
|
||||
<string name="input_content_here">Input content here</string>
|
||||
<string name="encode">ENCODE</string>
|
||||
<string name="error_no_content">Missing content</string>
|
||||
<string name="error_encoding_barcode">Barcode cannot be generated</string>
|
||||
<string name="view_barcode">View barcode</string>
|
||||
<string name="info">Info</string>
|
||||
<string name="switch_camera">Switch camera</string>
|
||||
<string name="history">History</string>
|
||||
<string name="use_history">Save scan history</string>
|
||||
<string name="remove_scan">Remove scan</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>
|
||||
<string name="no_barcode_found">No barcode found</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="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>
|
||||
</resources>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
name="Button"
|
||||
parent="@android:style/Widget.Button">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">36dp</item>
|
||||
<item name="android:layout_height">48dp</item>
|
||||
<item name="android:layout_gravity">center_horizontal</item>
|
||||
<item name="android:background">@drawable/button_raised</item>
|
||||
<item name="android:textColor">@color/button_text</item>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma version(1)
|
||||
#pragma rs java_package_name(de.markusfisch.android.binaryeye.renderscript)
|
||||
#pragma rs_fp_relaxed
|
||||
|
||||
rs_allocation inImage;
|
||||
int inWidth;
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package de.markusfisch.android.binaryeye.actions.wifi
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import junit.framework.TestCase.assertNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import junit.framework.TestCase.fail
|
||||
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 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")
|
||||
}
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -1,22 +1,17 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.21'
|
||||
ext.tools_version = '3.3.2'
|
||||
ext.kotlin_version = '1.3.31'
|
||||
ext.tools_version = '3.4.2'
|
||||
ext.build_tools_version = '28.0.3'
|
||||
ext.sdk_version = 28
|
||||
ext.support_version = '25.3.1'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$tools_version"
|
||||
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
@@ -27,8 +22,8 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,6 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
#Tue May 07 14:37:45 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+4
-10
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="google_play_icon.svg"
|
||||
inkscape:export-filename="/Users/markus/app_icon.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.80761719"
|
||||
inkscape:cx="-59.743651"
|
||||
inkscape:cy="256"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="1028"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="false" />
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-540.36218)">
|
||||
<rect
|
||||
style="opacity:1;fill:#b6d46f;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect4145"
|
||||
width="512"
|
||||
height="512"
|
||||
x="0"
|
||||
y="540.36218"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.1254902;fill-rule:nonzero;stroke:none;stroke-width:8.60725021;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 259.47183,657.46384 c -36.63695,0 -73.27309,13.80545 -101.20422,41.42278 -55.86225,55.2346 -55.68941,144.72925 0,200.13856 l 193.69605,192.72152 202.57936,4.825 L 554.16705,891.40531 360.67609,698.88662 C 332.83155,671.18195 296.10884,657.46389 259.47188,657.46384 Z"
|
||||
id="path5462"
|
||||
sodipodi:nodetypes="ssscccss" />
|
||||
<circle
|
||||
r="141.46466"
|
||||
cy="799.18176"
|
||||
cx="258.80164"
|
||||
id="circle5467"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="circle5451"
|
||||
cx="254.77118"
|
||||
cy="803.87579"
|
||||
r="77.382751" />
|
||||
<circle
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="circle5455"
|
||||
cx="307.03757"
|
||||
cy="747.37183"
|
||||
r="50.853714" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(5.4385115,0,0,5.4385115,-615.98708,-4631.8746)"><flowRegion
|
||||
id="flowRegion3778"><rect
|
||||
id="rect3780"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara3782" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:217.54046631px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="24816.699"
|
||||
y="13685.234"
|
||||
id="text5084"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086"
|
||||
x="24816.699"
|
||||
y="13685.234" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776-6"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(5.4385115,0,0,5.4385115,2280.8097,-4663.4521)"><flowRegion
|
||||
id="flowRegion3778-7"><rect
|
||||
id="rect3780-2"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara3782-0" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:217.54046631px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="27713.496"
|
||||
y="13653.656"
|
||||
id="text5084-0"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086-1"
|
||||
x="27713.496"
|
||||
y="13653.656" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776-8"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(5.4385115,0,0,5.4385115,2962.7932,-4497.8264)"><flowRegion
|
||||
id="flowRegion3778-4"><rect
|
||||
id="rect3780-5"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara3782-2" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:217.54046631px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="28395.479"
|
||||
y="13819.282"
|
||||
id="text5084-3"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086-18"
|
||||
x="28395.479"
|
||||
y="13819.282" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776-2"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.31339216px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(5.4385115,0,0,5.4385115,-615.98708,-4631.8746)"><flowRegion
|
||||
id="flowRegion3778-41"><rect
|
||||
id="rect3780-54"
|
||||
width="3.1272094"
|
||||
height="7.6562715"
|
||||
x="32.001694"
|
||||
y="919.38837" /></flowRegion><flowPara
|
||||
id="flowPara3782-28" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:23.45843315px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="2270.0874"
|
||||
y="2308.2637"
|
||||
id="text5084-7"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086-8"
|
||||
x="2270.0874"
|
||||
y="2308.2637" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776-6-6"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(0.83313297,0,0,0.83313363,76.628678,129.4161)"><flowRegion
|
||||
id="flowRegion3778-9"><rect
|
||||
id="rect3780-6"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara3782-8" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:33.32533264px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="3972.6985"
|
||||
y="2935.4377"
|
||||
id="text5084-3-4"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086-9"
|
||||
x="3972.6985"
|
||||
y="2935.4377" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776-0"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(0.83313297,0,0,0.83313363,-535.58967,134.25349)"><flowRegion
|
||||
id="flowRegion3778-6"><rect
|
||||
id="rect3780-9"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara3782-3" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:33.32533264px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="3360.4797"
|
||||
y="2940.2754"
|
||||
id="text5084-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086-96"
|
||||
x="3360.4797"
|
||||
y="2940.2754" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5144"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(0.90032116,0,0,0.90032187,-704.04381,822.02136)"><flowRegion
|
||||
id="flowRegion5146"><rect
|
||||
id="rect5148"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara5150" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:36.01285934px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="3506.2246"
|
||||
y="3854.3357"
|
||||
id="text5152"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5154"
|
||||
x="3506.2246"
|
||||
y="3854.3357" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5388"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(1.1287608,0,0,1.1287617,-518.09387,856.33891)"><flowRegion
|
||||
id="flowRegion5390"><rect
|
||||
id="rect5392"
|
||||
width="29"
|
||||
height="71"
|
||||
x="52"
|
||||
y="60" /></flowRegion><flowPara
|
||||
id="flowPara5394" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:45.15045166px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="4760.4517"
|
||||
y="4658.0464"
|
||||
id="text5396"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5398"
|
||||
x="4760.4517"
|
||||
y="4658.0464" /></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot3776-2-9"
|
||||
style="font-style:normal;font-weight:normal;font-size:33.32533264px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"><flowRegion
|
||||
id="flowRegion3778-7-9"><rect
|
||||
id="rect3780-0"
|
||||
width="24.160856"
|
||||
height="59.152489"
|
||||
x="920.29907"
|
||||
y="14.729361" /></flowRegion><flowPara
|
||||
id="flowPara3782-5" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:33.32533264px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="4773.0464"
|
||||
y="2770.7634"
|
||||
id="text5084-7-9"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5086-0"
|
||||
x="4773.0464"
|
||||
y="2770.7634" /></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user