Update plugin com.android.application to v8.3.1 #3

Merged
bot merged 3 commits from renovate/com.android.application-8.x into main 2024-03-23 02:05:29 +01:00
33 changed files with 235 additions and 519 deletions

View File

@ -20,12 +20,13 @@
</activity> </activity>
<receiver <receiver
android:name=".pluginSDK.PluginAccessReceiver" android:name=".pluginSDK.PluginAccessBroadcastReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" /> <action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" /> <action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" /> <action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
</intent-filter> </intent-filter>
</receiver> </receiver>
@ -34,16 +35,13 @@
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="keepass2android.ACTION_OPEN_ENTRY" /> <action android:name="keepass2android.ACTION_OPEN_ENTRY" />
<action android:name="keepass2android.ACTION_ENTRY_OUTPUT_MODIFIED" />
<action android:name="keepass2android.ACTION_CLOSE_ENTRY_VIEW" /> <action android:name="keepass2android.ACTION_CLOSE_ENTRY_VIEW" />
<action android:name="keepass2android.ACTION_ENTRY_ACTION_SELECTED" /> <action android:name="keepass2android.ACTION_ADD_ENTRY_ACTION" />
<action android:name="keepass2android.ACTION_LOCK_DATABASE" />
<action android:name="keepass2android.ACTION_UNLOCK_DATABASE" />
<action android:name="keepass2android.ACTION_CLOSE_DATABASE" />
<action android:name="keepass2android.ACTION_OPEN_DATABASE" />
</intent-filter> </intent-filter>
</receiver> </receiver>
</application> </application>
</manifest> </manifest>

View File

@ -1,7 +1,9 @@
package net.helcel.fidelity.activity package net.helcel.fidelity.activity
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.os.Bundle import android.os.Bundle
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -10,10 +12,10 @@ import net.helcel.fidelity.activity.fragment.Launcher
import net.helcel.fidelity.databinding.ActMainBinding import net.helcel.fidelity.databinding.ActMainBinding
import net.helcel.fidelity.tools.CacheManager import net.helcel.fidelity.tools.CacheManager
@SuppressLint("SourceLockedOrientationActivity")
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActMainBinding private lateinit var binding: ActMainBinding
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
@ -23,25 +25,26 @@ class MainActivity : AppCompatActivity() {
this.getSharedPreferences(CacheManager.PREF_NAME, Context.MODE_PRIVATE) this.getSharedPreferences(CacheManager.PREF_NAME, Context.MODE_PRIVATE)
CacheManager.loadFidelity(sharedPreferences) CacheManager.loadFidelity(sharedPreferences)
binding = ActMainBinding.inflate(layoutInflater) binding = ActMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
onBackPressedDispatcher.addCallback(this) { onBackPressedDispatcher.addCallback(this) {
if (supportFragmentManager.backStackEntryCount > 0) { if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStackImmediate() supportFragmentManager.popBackStackImmediate()
loadLauncher()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else { } else {
finish() finish()
} }
} }
if (savedInstanceState == null) if (savedInstanceState == null)
loadLauncher() loadLauncher()
} }
private fun loadLauncher() { private fun loadLauncher() {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.container, Launcher()) .replace(R.id.container, Launcher())
.commit() .commit()
} }
} }

View File

@ -26,7 +26,7 @@ class CreateEntry : Fragment() {
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private lateinit var binding: FragCreateEntryBinding private lateinit var binding: FragCreateEntryBinding
private val resultLauncherAdd = KeepassWrapper.resultLauncherAdd(this) { private val resultLauncherAdd = KeepassWrapper.resultLauncher(this) {
val r = KeepassWrapper.entryExtract(it) val r = KeepassWrapper.entryExtract(it)
if (!KeepassWrapper.isProtected(it)) { if (!KeepassWrapper.isProtected(it)) {
CacheManager.addFidelity(r) CacheManager.addFidelity(r)
@ -67,7 +67,7 @@ class CreateEntry : Fragment() {
ErrorToaster.formIncomplete(requireActivity()) ErrorToaster.formIncomplete(requireActivity())
} else { } else {
val kpentry = KeepassWrapper.entryCreate( val kpEntry = KeepassWrapper.entryCreate(
this, this,
binding.editTextTitle.text.toString(), binding.editTextTitle.text.toString(),
binding.editTextCode.text.toString(), binding.editTextCode.text.toString(),
@ -77,8 +77,8 @@ class CreateEntry : Fragment() {
try { try {
resultLauncherAdd.launch( resultLauncherAdd.launch(
Kp2aControl.getAddEntryIntent( Kp2aControl.getAddEntryIntent(
kpentry.first, kpEntry.first,
kpentry.second kpEntry.second
) )
) )
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
@ -108,23 +108,21 @@ class CreateEntry : Fragment() {
binding.editTextCode.error = e.message binding.editTextCode.error = e.message
} catch (e: Exception) { } catch (e: Exception) {
binding.imageViewPreview.setImageBitmap(null) binding.imageViewPreview.setImageBitmap(null)
println(e.javaClass)
println(e.message)
e.printStackTrace() e.printStackTrace()
} }
} }
private fun isValid(): Boolean { private fun isValid(): Boolean {
var valid = true var valid = true
if (binding.editTextTitle.text!!.isEmpty()) { if (binding.editTextTitle.text.isNullOrEmpty()) {
valid = false valid = false
binding.editTextTitle.error = "Title cannot be empty" binding.editTextTitle.error = "Title cannot be empty"
} }
if (binding.editTextCode.text!!.isEmpty()) { if (binding.editTextCode.text.isNullOrEmpty()) {
valid = false valid = false
binding.editTextCode.error = "Code cannot be empty" binding.editTextCode.error = "Code cannot be empty"
} }
if (binding.editTextFormat.text!!.isEmpty()) { if (binding.editTextFormat.text.isNullOrEmpty()) {
valid = false valid = false
binding.editTextFormat.error = "Format cannot be empty" binding.editTextFormat.error = "Format cannot be empty"
} }

View File

@ -23,7 +23,7 @@ class Launcher : Fragment() {
private lateinit var binding: FragLauncherBinding private lateinit var binding: FragLauncherBinding
private lateinit var fidelityListAdapter: FidelityListAdapter private lateinit var fidelityListAdapter: FidelityListAdapter
private val resultLauncherQuery = KeepassWrapper.resultLauncherQuery(this) { private val resultLauncherQuery = KeepassWrapper.resultLauncher(this) {
val r = KeepassWrapper.entryExtract(it) val r = KeepassWrapper.entryExtract(it)
if (!KeepassWrapper.isProtected(it)) { if (!KeepassWrapper.isProtected(it)) {
CacheManager.addFidelity(r) CacheManager.addFidelity(r)
@ -80,7 +80,7 @@ class Launcher : Fragment() {
private fun startGetFromKeepass() { private fun startGetFromKeepass() {
try { try {
this.resultLauncherQuery.launch(Kp2aControl.queryEntryIntentForOwnPackage) this.resultLauncherQuery.launch(Kp2aControl.getQueryEntryForOwnPackageIntent())
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
ErrorToaster.noKP2AFound(requireActivity()) ErrorToaster.noKP2AFound(requireActivity())
} }

View File

@ -27,7 +27,6 @@ class Scanner : Fragment() {
private var code: String = "" private var code: String = ""
private var fmt: String = "" private var fmt: String = ""
private var valid: Boolean = false
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -35,13 +34,14 @@ class Scanner : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragScannerBinding.inflate(layoutInflater) binding = FragScannerBinding.inflate(layoutInflater)
binding.bottomText.setOnClickListener { binding.btnScanDone.setOnClickListener {
startCreateEntry() startCreateEntry()
} }
when (hasCameraPermission()) { when (hasCameraPermission()) {
true -> bindCameraUseCases() true -> bindCameraUseCases()
else -> requestPermission() else -> requestPermission()
} }
binding.btnScanDone.isEnabled = false
return binding.root return binding.root
} }
@ -92,9 +92,10 @@ class Scanner : Fragment() {
if (code != null && format != null) { if (code != null && format != null) {
this.code = code this.code = code
this.fmt = format this.fmt = format
this.valid = true binding.btnScanDone.isEnabled = true
} else { } else {
this.valid = false binding.btnScanDone.isEnabled = false
} }
} }
try { try {
@ -111,6 +112,4 @@ class Scanner : Fragment() {
} }
}, ContextCompat.getMainExecutor(requireContext())) }, ContextCompat.getMainExecutor(requireContext()))
} }
} }

View File

@ -1,10 +1,14 @@
package net.helcel.fidelity.activity.fragment package net.helcel.fidelity.activity.fragment
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.zxing.FormatException import com.google.zxing.FormatException
import net.helcel.fidelity.databinding.FragViewEntryBinding import net.helcel.fidelity.databinding.FragViewEntryBinding
@ -12,16 +16,14 @@ import net.helcel.fidelity.tools.BarcodeGenerator.generateBarcode
import net.helcel.fidelity.tools.ErrorToaster import net.helcel.fidelity.tools.ErrorToaster
import net.helcel.fidelity.tools.KeepassWrapper import net.helcel.fidelity.tools.KeepassWrapper
@SuppressLint("SourceLockedOrientationActivity")
class ViewEntry : Fragment() { class ViewEntry : Fragment() {
private lateinit var binding: FragViewEntryBinding private lateinit var binding: FragViewEntryBinding
private var title: String? = null private var title: String? = null
private var code: String? = null private var code: String? = null
private var fmt: String? = null private var fmt: String? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -33,8 +35,15 @@ class ViewEntry : Fragment() {
code = res.second code = res.second
fmt = res.third fmt = res.third
adjustLayout()
updatePreview() updatePreview()
updateLayout()
binding.imageViewPreview.setOnClickListener {
requireActivity().requestedOrientation =
if (isLandscape()) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
else ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
return binding.root return binding.root
} }
@ -42,7 +51,7 @@ class ViewEntry : Fragment() {
binding.title.text = title binding.title.text = title
try { try {
val barcodeBitmap = generateBarcode( val barcodeBitmap = generateBarcode(
code!!, fmt!!, 1024 code, fmt, 1024
) )
binding.imageViewPreview.setImageBitmap(barcodeBitmap) binding.imageViewPreview.setImageBitmap(barcodeBitmap)
} catch (e: FormatException) { } catch (e: FormatException) {
@ -53,23 +62,25 @@ class ViewEntry : Fragment() {
ErrorToaster.invalidFormat(requireActivity()) ErrorToaster.invalidFormat(requireActivity())
} catch (e: Exception) { } catch (e: Exception) {
binding.imageViewPreview.setImageBitmap(null) binding.imageViewPreview.setImageBitmap(null)
println(e.javaClass)
println(e.message)
e.printStackTrace() e.printStackTrace()
} }
} }
private fun updateLayout() {
override fun onConfigurationChanged(newConfig: Configuration) { if (isLandscape()) {
super.onConfigurationChanged(newConfig)
adjustLayout()
}
private fun adjustLayout() {
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
binding.title.visibility = View.GONE binding.title.visibility = View.GONE
setScreenBrightness(BRIGHTNESS_OVERRIDE_FULL)
} else { } else {
binding.title.visibility = View.VISIBLE binding.title.visibility = View.VISIBLE
setScreenBrightness(BRIGHTNESS_OVERRIDE_NONE)
} }
} }
private fun isLandscape(): Boolean {
return (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
}
private fun setScreenBrightness(brightness: Float?) {
requireActivity().window?.attributes?.screenBrightness = brightness
}
} }

View File

@ -0,0 +1,45 @@
package net.helcel.fidelity.activity.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.View
class ScannerView : View {
private val overlayPaint = Paint().apply {
color = Color.parseColor("#80000000") // Semi-transparent black
style = Paint.Style.FILL
}
private val clearPaint = Paint().apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), overlayPaint)
val centerX = width / 2f
val centerY = height / 2f
val squareSize = 0.75f * width.coerceAtMost(height)
canvas.drawRect(
centerX - squareSize / 2, centerY - squareSize / 2,
centerX + squareSize / 2, centerY + squareSize / 2, clearPaint
)
}
}

View File

@ -2,141 +2,87 @@ package net.helcel.fidelity.pluginSDK
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.text.TextUtils
import android.util.Log
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONException import org.json.JSONException
class PluginAccessException(msg: String) : Exception(msg)
object AccessManager { object AccessManager {
private const val _tag = "Kp2aPluginSDK"
private const val PREF_KEY_SCOPE = "scope" private const val PREF_KEY_SCOPE = "scope"
private const val PREF_KEY_TOKEN = "token" private const val PREF_KEY_TOKEN = "token"
private fun stringArrayToString(values: ArrayList<String?>): String? { private fun stringArrayToString(values: ArrayList<String?>): String? {
if (values.isEmpty()) return null
val a = JSONArray() val a = JSONArray()
for (i in values.indices) { values.forEach { a.put(it) }
a.put(values[i]) return a.toString()
}
return if (values.isNotEmpty()) {
a.toString()
} else {
null
}
} }
private fun stringToStringArray(s: String?): ArrayList<String> { private fun stringToStringArray(s: String?): ArrayList<String> {
val strings = ArrayList<String>() val strings = ArrayList<String>()
if (!TextUtils.isEmpty(s)) { if (s.isNullOrEmpty()) return strings
try { try {
val a = JSONArray(s) val a = JSONArray(s)
for (i in 0 until a.length()) { for (i in 0 until a.length())
val url = a.optString(i) strings.add(a.optString(i))
strings.add(url)
}
} catch (e: JSONException) { } catch (e: JSONException) {
e.printStackTrace() e.printStackTrace()
} }
}
return strings return strings
} }
fun storeAccessToken( fun storeAccessToken(
ctx: Context, ctx: Context,
hostPackage: String, hostPackage: String?,
accessToken: String, accessToken: String?,
scopes: ArrayList<String?> scopes: ArrayList<String?>
) { ) {
val prefs = getPrefsForHost(ctx, hostPackage) val prefs = getPrefsForHost(ctx, hostPackage)
val edit = prefs.edit() val edit = prefs.edit()
edit.putString(PREF_KEY_TOKEN, accessToken) edit.putString(PREF_KEY_TOKEN, accessToken)
val scopesString = stringArrayToString(scopes) val scopesString = stringArrayToString(scopes)
edit.putString(PREF_KEY_SCOPE, scopesString) edit.putString(PREF_KEY_SCOPE, scopesString)
edit.apply() edit.apply()
Log.d(
_tag,
"stored access token " + accessToken.substring(
0,
4
) + "... for " + scopes.size + " scopes (" + scopesString + ")."
)
val hostPrefs = ctx.getSharedPreferences("KP2A.PluginAccess.hosts", Context.MODE_PRIVATE) val hostPrefs = ctx.getSharedPreferences("KP2A.PluginAccess.hosts", Context.MODE_PRIVATE)
if (!hostPrefs.contains(hostPackage)) { if (!hostPrefs.contains(hostPackage))
hostPrefs.edit().putString(hostPackage, "").apply() hostPrefs.edit().putString(hostPackage, "").apply()
} }
}
fun preparePopup(popupMenu: Any) {
try {
val fields = popupMenu.javaClass.declaredFields
for (field in fields) {
if ("mPopup" == field.name) {
field.isAccessible = true
val menuPopupHelper = field[popupMenu]
val classPopupHelper = Class.forName(
menuPopupHelper
.javaClass.name
)
val setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", Boolean::class.javaPrimitiveType
)
setForceIcons.invoke(menuPopupHelper, true)
break
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getPrefsForHost( private fun getPrefsForHost(
ctx: Context, ctx: Context,
hostPackage: String hostPackage: String?
): SharedPreferences { ): SharedPreferences {
val prefs = ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE) return ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE)
return prefs
} }
fun tryGetAccessToken(ctx: Context, hostPackage: String, scopes: ArrayList<String?>): String? { fun tryGetAccessToken(ctx: Context, hostPackage: String?, scopes: ArrayList<String?>): String? {
if (TextUtils.isEmpty(hostPackage)) { if (hostPackage.isNullOrEmpty()) return null
Log.d(_tag, "hostPackage is empty!")
return null
}
Log.d(_tag, "trying to find prefs for $hostPackage")
val prefs = getPrefsForHost(ctx, hostPackage) val prefs = getPrefsForHost(ctx, hostPackage)
val scopesString = prefs.getString(PREF_KEY_SCOPE, "") val scopesString = prefs.getString(PREF_KEY_SCOPE, "")
Log.d(_tag, "available scopes: $scopesString")
val currentScope = stringToStringArray(scopesString) val currentScope = stringToStringArray(scopesString)
if (isSubset(scopes, currentScope)) { if (!isSubset(scopes, currentScope))
return prefs.getString(PREF_KEY_TOKEN, null)
} else {
Log.d(_tag, "looks like scope changed. Access token invalid.")
return null return null
} return prefs.getString(PREF_KEY_TOKEN, null)
} }
private fun isSubset( private fun isSubset(
requiredScopes: ArrayList<String?>, requiredScopes: ArrayList<String?>,
availableScopes: ArrayList<String> availableScopes: ArrayList<String>
): Boolean { ): Boolean {
for (r in requiredScopes) { return availableScopes.containsAll(requiredScopes)
if (availableScopes.indexOf(r) < 0) {
Log.d(_tag, "Scope " + r + " not available. " + availableScopes.size)
return false
}
}
return true
} }
fun removeAccessToken( fun removeAccessToken(
ctx: Context, hostPackage: String, ctx: Context, hostPackage: String?,
accessToken: String accessToken: String?
) { ) {
val prefs = getPrefsForHost(ctx, hostPackage) val prefs = getPrefsForHost(ctx, hostPackage)
Log.d(_tag, "removing AccessToken.")
if (prefs.getString(PREF_KEY_TOKEN, "") == accessToken) { if (prefs.getString(PREF_KEY_TOKEN, "") == accessToken) {
val edit = prefs.edit() val edit = prefs.edit()
edit.clear() edit.clear()
@ -149,31 +95,11 @@ object AccessManager {
} }
} }
fun getAllHostPackages(ctx: Context): Set<String> {
val prefs = ctx.getSharedPreferences("KP2A.PluginAccess.hosts", Context.MODE_PRIVATE)
val result: MutableSet<String> = HashSet()
for (host in prefs.all.keys) {
try {
val info = ctx.packageManager.getPackageInfo(host, PackageManager.GET_META_DATA)
//if we get here, the package is still there
result.add(host)
} catch (e: PackageManager.NameNotFoundException) {
//host gone. ignore.
}
}
return result
}
/**
* Returns a valid access token or throws PluginAccessException
*/
fun getAccessToken( fun getAccessToken(
context: Context, hostPackage: String, context: Context, hostPackage: String?,
scopes: ArrayList<String?> scopes: ArrayList<String?>
): String { ): String {
val accessToken = tryGetAccessToken(context, hostPackage, scopes) return tryGetAccessToken(context, hostPackage, scopes)
?: throw PluginAccessException(hostPackage, scopes) ?: throw PluginAccessException(hostPackage + scopes)
return accessToken
} }
} }

View File

@ -0,0 +1,9 @@
package net.helcel.fidelity.pluginSDK
object KeepassDef {
var TitleField: String = "Title"
var UserNameField: String = "UserName"
var PasswordField: String = "Password"
var UrlField: String = "URL"
var NotesField: String = "Notes"
}

View File

@ -1,45 +0,0 @@
package net.helcel.fidelity.pluginSDK
object KeepassDefs {
/// <summary>
/// Default identifier string for the title field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
var TitleField: String = "Title"
/// <summary>
/// Default identifier string for the user name field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
private var UserNameField: String = "UserName"
/// <summary>
/// Default identifier string for the password field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
private var PasswordField: String = "Password"
/// <summary>
/// Default identifier string for the URL field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
var UrlField: String = "URL"
/// <summary>
/// Default identifier string for the notes field. Should not contain
/// spaces, tabs or other whitespace.
/// </summary>
private var NotesField: String = "Notes"
fun IsStandardField(strFieldName: String?): Boolean {
if (strFieldName == null) return false
if (strFieldName == TitleField) return true
if (strFieldName == UserNameField) return true
if (strFieldName == PasswordField) return true
if (strFieldName == UrlField) return true
if (strFieldName == NotesField) return true
return false
}
}

View File

@ -1,96 +1,38 @@
package net.helcel.fidelity.pluginSDK package net.helcel.fidelity.pluginSDK
import android.content.Intent import android.content.Intent
import android.text.TextUtils
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
object Kp2aControl { object Kp2aControl {
/**
* Creates and returns an intent to launch Keepass2Android for adding an entry with the given fields.
* @param fields Key/Value pairs of the field values. See KeepassDefs for standard keys.
* @param protectedFields List of keys of the protected fields.
* @return Intent to start Keepass2Android.
* @throws JSONException
*/
fun getAddEntryIntent(
fields: HashMap<String?, String?>?,
protectedFields: ArrayList<String?>?
): Intent {
return getAddEntryIntent(JSONObject((fields as Map<*, *>?)!!).toString(), protectedFields)
}
private fun getAddEntryIntent( fun getAddEntryIntent(
outputData: String?, fields: HashMap<String?, String?>,
protectedFields: ArrayList<String?>? protectedFields: ArrayList<String?>?
): Intent { ): Intent {
val outputData = JSONObject((fields as Map<*, *>)).toString()
val startKp2aIntent = Intent(Strings.ACTION_START_WITH_TASK) val startKp2aIntent = Intent(Strings.ACTION_START_WITH_TASK)
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT) startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT)
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
startKp2aIntent.putExtra("KP2A_APPTASK", "CreateEntryThenCloseTask") startKp2aIntent.putExtra("KP2A_APPTASK", "CreateEntryThenCloseTask")
startKp2aIntent.putExtra("ShowUserNotifications", "false") //KP2A expects a StringExtra startKp2aIntent.putExtra("ShowUserNotifications", "false")
startKp2aIntent.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, outputData) startKp2aIntent.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, outputData)
if (protectedFields != null) startKp2aIntent.putStringArrayListExtra( if (protectedFields != null)
startKp2aIntent.putStringArrayListExtra(
Strings.EXTRA_PROTECTED_FIELDS_LIST, Strings.EXTRA_PROTECTED_FIELDS_LIST,
protectedFields protectedFields
) )
return startKp2aIntent return startKp2aIntent
} }
fun getQueryEntryForOwnPackageIntent(): Intent {
/** return Intent(Strings.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
* Creates an intent to open a Password Entry matching searchText
* @param searchText queryString
* @param showUserNotifications if true, the notifications (copy to clipboard, keyboard) are displayed
* @param closeAfterOpen if true, the entry is opened and KP2A is immediately closed
* @return Intent to start KP2A with
*/
fun getOpenEntryIntent(
searchText: String?,
showUserNotifications: Boolean,
closeAfterOpen: Boolean
): Intent {
val startKp2aIntent = Intent(Strings.ACTION_START_WITH_TASK)
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT)
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
startKp2aIntent.putExtra("KP2A_APPTASK", "SearchUrlTask")
startKp2aIntent.putExtra("ShowUserNotifications", showUserNotifications.toString())
startKp2aIntent.putExtra("CloseAfterCreate", closeAfterOpen.toString())
startKp2aIntent.putExtra("UrlToSearch", searchText)
return startKp2aIntent
} }
/** fun getEntryFieldsFromIntent(intent: Intent?): HashMap<String, String> {
* Creates an intent to query a password entry from KP2A. The credentials are returned as Activity result.
* @param searchText Text to search for. Should be a URL or "androidapp://com.my.package."
* @return an Intent to start KP2A with
*/
fun getQueryEntryIntent(searchText: String?): Intent {
val i = Intent(Strings.ACTION_QUERY_CREDENTIALS)
if (!TextUtils.isEmpty(searchText)) i.putExtra(Strings.EXTRA_QUERY_STRING, searchText)
return i
}
val queryEntryIntentForOwnPackage: Intent
/**
* Creates an intent to query a password entry from KP2A, matching to the current app's package .
* The credentials are returned as Activity result.
* This requires SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE.
* @return an Intent to start KP2A with
*/
get() = Intent(Strings.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
/**
* Converts the entry fields returned in an intent from a query to a hashmap.
* @param intent data received in onActivityResult after getQueryEntryIntent(ForOwnPackage)
* @return HashMap with keys = field names (see KeepassDefs for standard keys) and values = values
*/
fun getEntryFieldsFromIntent(intent: Intent): HashMap<String, String> {
val res = HashMap<String, String>() val res = HashMap<String, String>()
try { try {
val json = JSONObject(intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!!) val json = JSONObject(intent?.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!!)
val iter = json.keys() val iter = json.keys()
while (iter.hasNext()) { while (iter.hasNext()) {
val key = iter.next() val key = iter.next()

View File

@ -3,55 +3,29 @@ package net.helcel.fidelity.pluginSDK
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
/** class PluginAccessBroadcastReceiver : BroadcastReceiver() {
* Broadcast flow between Host and Plugin
* ======================================
*
* The host is responsible for deciding when to initiate the session. It
* should initiate the session as soon as plugins are required or when a plugin
* has been updated through the OS.
* It will then send a broadcast to request the currently required scope.
* The plugin then sends a broadcast to the app which scope is required. If an
* access token is already available, it's sent along with the requset.
*
* If a previous permission has been revoked (or the app settings cleared or the
* permissions have been extended or the token is invalid for any other reason)
* the host will answer with a Revoked-Permission broadcast (i.e. the plugin is
* unconnected.)
*
* Unconnected plugins must be permitted by the user (requiring user action).
* When the user grants access, the plugin will receive an access token for
* the host. This access token is valid for the requested scope. If the scope
* changes (e.g after an update of the plugin), the access token becomes invalid.
*
*/
abstract class PluginAccessBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) { override fun onReceive(ctx: Context, intent: Intent) {
val action = intent.action val action = intent.action ?: return
Log.d(_tag, "received broadcast with action=$action")
if (action == null) return
when (action) { when (action) {
Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent) Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent)
Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent) Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent)
Strings.ACTION_REVOKE_ACCESS -> revokeAccess(ctx, intent) Strings.ACTION_REVOKE_ACCESS -> revokeAccess(ctx, intent)
else -> {} else -> println(action)
} }
} }
private fun revokeAccess(ctx: Context, intent: Intent) { private fun revokeAccess(ctx: Context, intent: Intent) {
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER) val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN) val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN)
AccessManager.removeAccessToken(ctx, senderPackage!!, accessToken!!) AccessManager.removeAccessToken(ctx, senderPackage, accessToken)
} }
private fun receiveAccess(ctx: Context, intent: Intent) { private fun receiveAccess(ctx: Context, intent: Intent) {
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER) val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN) val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN)
AccessManager.storeAccessToken(ctx, senderPackage!!, accessToken!!, scopes) AccessManager.storeAccessToken(ctx, senderPackage, accessToken, scopes)
} }
private fun requestAccess(ctx: Context, intent: Intent) { private fun requestAccess(ctx: Context, intent: Intent) {
@ -62,21 +36,18 @@ abstract class PluginAccessBroadcastReceiver : BroadcastReceiver() {
rpi.putExtra(Strings.EXTRA_SENDER, ctx.packageName) rpi.putExtra(Strings.EXTRA_SENDER, ctx.packageName)
rpi.putExtra(Strings.EXTRA_REQUEST_TOKEN, requestToken) rpi.putExtra(Strings.EXTRA_REQUEST_TOKEN, requestToken)
val token: String? = AccessManager.tryGetAccessToken(ctx, senderPackage!!, scopes) val token: String? = AccessManager.tryGetAccessToken(ctx, senderPackage, scopes)
rpi.putExtra(Strings.EXTRA_ACCESS_TOKEN, token) rpi.putExtra(Strings.EXTRA_ACCESS_TOKEN, token)
rpi.putStringArrayListExtra(Strings.EXTRA_SCOPES, scopes) rpi.putStringArrayListExtra(Strings.EXTRA_SCOPES, scopes)
Log.d(_tag, "requesting access for " + scopes.size + " tokens.")
ctx.sendBroadcast(rpi) ctx.sendBroadcast(rpi)
} }
/** private val scopes: ArrayList<String?> = ArrayList(
* listOf(
* @return the list of required scopes for this plugin. Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE,
*/ Strings.SCOPE_DATABASE_ACTIONS,
abstract val scopes: ArrayList<String?> Strings.SCOPE_CURRENT_ENTRY,
)
companion object { )
private const val _tag = "Kp2aPluginSDK"
}
} }

View File

@ -1,14 +0,0 @@
package net.helcel.fidelity.pluginSDK
class PluginAccessException : Exception {
constructor(what: String?) : super(what)
constructor(hostPackage: String?, scopes: ArrayList<String?>)
companion object {
/**
*
*/
private const val serialVersionUID = 1L
}
}

View File

@ -1,16 +0,0 @@
package net.helcel.fidelity.pluginSDK
import kotlin.collections.ArrayList
class PluginAccessReceiver : PluginAccessBroadcastReceiver() {
override val scopes: ArrayList<String?> = ArrayList()
init {
this.scopes.add(Strings.SCOPE_DATABASE_ACTIONS)
this.scopes.add(Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
}
}

View File

@ -22,7 +22,8 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
get() { get() {
val res = HashMap<String, String>() val res = HashMap<String, String>()
try { try {
val json = JSONObject(_intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!!) val json =
JSONObject(_intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA) ?: "")
val iter = json.keys() val iter = json.keys()
while (iter.hasNext()) { while (iter.hasNext()) {
val key = iter.next() val key = iter.next()
@ -54,14 +55,13 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID) get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID)
@Throws(PluginAccessException::class)
fun setEntryField(fieldId: String?, fieldValue: String?, isProtected: Boolean) { fun setEntryField(fieldId: String?, fieldValue: String?, isProtected: Boolean) {
val i = Intent(Strings.ACTION_SET_ENTRY_FIELD) val i = Intent(Strings.ACTION_SET_ENTRY_FIELD)
val scope = ArrayList<String?>() val scope = ArrayList<String?>()
scope.add(Strings.SCOPE_CURRENT_ENTRY) scope.add(Strings.SCOPE_CURRENT_ENTRY)
i.putExtra( i.putExtra(
Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken( Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(
context, hostPackage!!, scope context, hostPackage, scope
) )
) )
i.setPackage(hostPackage) i.setPackage(hostPackage)
@ -132,7 +132,6 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
*/ */
get() = protectedFieldsListFromIntent get() = protectedFieldsListFromIntent
@Throws(PluginAccessException::class)
fun addEntryAction( fun addEntryAction(
actionDisplayText: String?, actionDisplayText: String?,
actionIconResourceId: Int, actionIconResourceId: Int,
@ -141,7 +140,6 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
addEntryFieldAction(null, null, actionDisplayText, actionIconResourceId, actionData) addEntryFieldAction(null, null, actionDisplayText, actionIconResourceId, actionData)
} }
@Throws(PluginAccessException::class)
fun addEntryFieldAction( fun addEntryFieldAction(
actionId: String?, actionId: String?,
fieldId: String?, fieldId: String?,
@ -191,24 +189,28 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
} }
override fun onReceive(ctx: Context, intent: Intent) { override fun onReceive(ctx: Context, intent: Intent) {
val action = intent.action val action = intent.action ?: return
Log.d( Log.d(
"KP2A.pluginsdk", "KP2A.pluginsdk",
"received broadcast in PluginActionBroadcastReceiver with action=$action" "received broadcast in PluginActionBroadcastReceiver with action=$action"
) )
if (action == null) return println(action)
if (action == Strings.ACTION_OPEN_ENTRY) {
openEntry(OpenEntryAction(ctx, intent)) when (action) {
} else if (action == Strings.ACTION_CLOSE_ENTRY_VIEW) { Strings.ACTION_OPEN_ENTRY -> openEntry(OpenEntryAction(ctx, intent))
closeEntryView(CloseEntryViewAction(ctx, intent)) Strings.ACTION_CLOSE_ENTRY_VIEW -> closeEntryView(CloseEntryViewAction(ctx, intent))
} else if (action == Strings.ACTION_ENTRY_ACTION_SELECTED) { Strings.ACTION_ENTRY_ACTION_SELECTED ->
actionSelected(ActionSelectedAction(ctx, intent)) actionSelected(ActionSelectedAction(ctx, intent))
} else if (action == Strings.ACTION_ENTRY_OUTPUT_MODIFIED) {
Strings.ACTION_ENTRY_OUTPUT_MODIFIED ->
entryOutputModified(EntryOutputModifiedAction(ctx, intent)) entryOutputModified(EntryOutputModifiedAction(ctx, intent))
} else if (action == Strings.ACTION_LOCK_DATABASE || action == Strings.ACTION_UNLOCK_DATABASE || action == Strings.ACTION_OPEN_DATABASE || action == Strings.ACTION_CLOSE_DATABASE) {
dbAction(DatabaseAction(ctx, intent)) Strings.ACTION_LOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent))
} else { Strings.ACTION_UNLOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent))
//TODO handle unexpected action Strings.ACTION_OPEN_DATABASE -> dbAction(DatabaseAction(ctx, intent))
Strings.ACTION_CLOSE_DATABASE -> dbAction(DatabaseAction(ctx, intent))
else -> println(action)
} }
} }

View File

@ -17,11 +17,6 @@ object Strings {
const val SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE = const val SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
"keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE" "keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
/**
* Plugin may query credentials for a deliberate package
*/
const val SCOPE_QUERY_CREDENTIALS = "keepass2android.SCOPE_QUERY_CREDENTIALS"
/** /**
* Extra key to transfer a (json serialized) list of scopes * Extra key to transfer a (json serialized) list of scopes
*/ */
@ -97,11 +92,6 @@ object Strings {
*/ */
const val EXTRA_ENTRY_ID = "keepass2android.EXTRA_ENTRY_DATA" const val EXTRA_ENTRY_ID = "keepass2android.EXTRA_ENTRY_DATA"
/**
* Json serialized data of the PwEntry (C# class) representing the opened entry.
* currently not implemented.
*/
//const val EXTRA_ENTRY_DATA = "keepass2android.EXTRA_ENTRY_DATA";
/** /**
* Json serialized list of fields, transformed using the database context (i.e. placeholders are replaced already) * Json serialized list of fields, transformed using the database context (i.e. placeholders are replaced already)
@ -157,13 +147,6 @@ object Strings {
const val ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE = const val ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
"keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE" "keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
/**
* Action when plugin wants to query credentials for a deliberate package
* The query string is passed as intent data
*/
const val ACTION_QUERY_CREDENTIALS = "keepass2android.ACTION_QUERY_CREDENTIALS"
/** /**
* Action for an intent from the plugin to KP2A to set (i.e. add or update) a field in the entry. * Action for an intent from the plugin to KP2A to set (i.e. add or update) a field in the entry.
* May be used to update existing or add new fields at any time while the entry is opened. * May be used to update existing or add new fields at any time while the entry is opened.
@ -189,7 +172,5 @@ object Strings {
const val EXTRA_FIELD_VALUE = "keepass2android.EXTRA_FIELD_VALUE" const val EXTRA_FIELD_VALUE = "keepass2android.EXTRA_FIELD_VALUE"
const val EXTRA_FIELD_PROTECTED = "keepass2android.EXTRA_FIELD_PROTECTED" const val EXTRA_FIELD_PROTECTED = "keepass2android.EXTRA_FIELD_PROTECTED"
const val PREFIX_STRING = "STRING_"
const val PREFIX_BINARY = "BINARY_"
} }

View File

@ -20,7 +20,6 @@ object BarcodeFormatConverter {
} }
} }
fun formatToString(f: Int): String { fun formatToString(f: Int): String {
return when (f) { return when (f) {
Barcode.FORMAT_CODE_128 -> "CODE_128" Barcode.FORMAT_CODE_128 -> "CODE_128"

View File

@ -19,25 +19,25 @@ object BarcodeGenerator {
android.graphics.Color.WHITE android.graphics.Color.WHITE
} }
fun generateBarcode(content: String, f: String, width: Int): Bitmap? { fun generateBarcode(content: String?, f: String?, w: Int): Bitmap? {
if (content.isEmpty() || f.isEmpty()) { if (content.isNullOrEmpty() || f.isNullOrEmpty()) {
return null return null
} }
try { try {
val format = stringToFormat(f) val format = stringToFormat(f)
val writer = MultiFormatWriter() val writer = MultiFormatWriter()
val height = (formatToRatio(format) * width).toInt() val height = (w * formatToRatio(format)).toInt()
val width = (w * 1.0f).toInt()
val bitMatrix: BitMatrix = writer.encode(content, format, width, height) val bitMatrix: BitMatrix = writer.encode(content, format, width, height)
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
for (x in 0 until width) { for (x in 0 until width) {
for (y in 0 until height) { for (y in 0 until height) {
bitmap.setPixel( bitmap.setPixel(
x, x, y, getPixelColor(bitMatrix, x, y)
y,
getPixelColor(bitMatrix, x, y)
) )
} }
} }
return bitmap return bitmap

View File

@ -15,9 +15,9 @@ import net.helcel.fidelity.tools.BarcodeFormatConverter.formatToString
import java.util.concurrent.Executors import java.util.concurrent.Executors
@OptIn(ExperimentalGetImage::class)
object BarcodeScanner { object BarcodeScanner {
@OptIn(ExperimentalGetImage::class)
private fun processImageProxy( private fun processImageProxy(
barcodeScanner: BarcodeScanner, barcodeScanner: BarcodeScanner,
imageProxy: ImageProxy, imageProxy: ImageProxy,
@ -33,8 +33,6 @@ object BarcodeScanner {
barcodeScanner.process(inputImage) barcodeScanner.process(inputImage)
.addOnSuccessListener { barcodeList -> .addOnSuccessListener { barcodeList ->
println(barcodeList.map { e -> e.displayValue })
println(barcodeList.map { e -> e.format })
val barcode = val barcode =
barcodeList.getOrNull(0) barcodeList.getOrNull(0)
if (barcode != null) if (barcode != null)

View File

@ -6,7 +6,7 @@ import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import net.helcel.fidelity.pluginSDK.KeepassDefs import net.helcel.fidelity.pluginSDK.KeepassDef
import net.helcel.fidelity.pluginSDK.Kp2aControl import net.helcel.fidelity.pluginSDK.Kp2aControl
object KeepassWrapper { object KeepassWrapper {
@ -25,8 +25,8 @@ object KeepassWrapper {
val fields = HashMap<String?, String?>() val fields = HashMap<String?, String?>()
val protected = ArrayList<String?>() val protected = ArrayList<String?>()
fields[KeepassDefs.TitleField] = title fields[KeepassDef.TitleField] = title
fields[KeepassDefs.UrlField] = fields[KeepassDef.UrlField] =
"androidapp://" + fragment.requireActivity().packageName "androidapp://" + fragment.requireActivity().packageName
fields[CODE_FIELD] = code fields[CODE_FIELD] = code
fields[FORMAT_FIELD] = format fields[FORMAT_FIELD] = format
@ -37,33 +37,17 @@ object KeepassWrapper {
} }
fun resultLauncherAdd( fun resultLauncher(
fragment: Fragment, fragment: Fragment,
callback: (HashMap<String, String>) -> Unit callback: (HashMap<String, String>) -> Unit
): ActivityResultLauncher<Intent> { ): ActivityResultLauncher<Intent> {
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
val credentials = Kp2aControl.getEntryFieldsFromIntent(
data!!
)
println(credentials)
callback(credentials)
}
}
}
fun resultLauncherQuery( println(result.resultCode)
fragment: Fragment, println(result.data.toString())
callback: (HashMap<String, String>) -> Unit
): ActivityResultLauncher<Intent> {
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data val credentials = Kp2aControl.getEntryFieldsFromIntent(result.data)
val credentials = Kp2aControl.getEntryFieldsFromIntent( println(credentials.toList().toString())
data!!
)
println(credentials)
callback(credentials) callback(credentials)
} }
} }
@ -71,7 +55,7 @@ object KeepassWrapper {
fun entryExtract(map: HashMap<String, String>): Triple<String?, String?, String?> { fun entryExtract(map: HashMap<String, String>): Triple<String?, String?, String?> {
return Triple( return Triple(
map[KeepassDefs.TitleField], map[KeepassDef.TitleField],
map[CODE_FIELD], map[CODE_FIELD],
map[FORMAT_FIELD] map[FORMAT_FIELD]
) )

View File

@ -1,21 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="52"
android:viewportHeight="52">
<group
android:translateX="-10"
android:translateY="-10">
<path
android:fillColor="#EA5A47"
android:pathData="M60.7,26.2c0,-7.2 -5.9,-13.1 -13.1,-13.1c-5,0 -9.3,2.8 -11.5,6.9c-2.2,-4.1 -6.6,-6.9 -11.5,-6.9c-7.2,0 -13.1,5.9 -13.1,13.1c0,3.1 1.1,6 2.9,8.2l0,0l21.8,27l21.8,-27l0,0C59.6,32.2 60.7,29.4 60.7,26.2z" />
<path
android:fillColor="#00000000"
android:pathData="M60.7,26.2c0,-7.2 -5.9,-13.1 -13.1,-13.1c-5,0 -9.3,2.8 -11.5,6.9c-2.2,-4.1 -6.6,-6.9 -11.5,-6.9c-7.2,0 -13.1,5.9 -13.1,13.1c0,3.1 1.1,6 2.9,8.2l0,0l21.8,27l21.8,-27l0,0C59.6,32.2 60.7,29.4 60.7,26.2z"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
</vector>

View File

@ -1,31 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="52"
android:viewportHeight="52">
<group
android:translateX="-10"
android:translateY="-10">
<path
android:fillColor="#00000000"
android:pathData="M30.735,34.656l-16.432,16.026l0,7.24l7.565,0l0,-4.637l5.125,0l0,-5.857l5.098,0l2.404,-2.404l0,-4.358l2.015,0"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M48.52,23.998m-3.952,0a3.952,3.952 0,1 1,7.904 0a3.952,3.952 0,1 1,-7.904 0"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="#00000000"
android:pathData="M34.226,31.178c-1.43,-4.238 -0.347,-9.221 3.18,-12.695c4.845,-4.772 12.465,-4.889 17.022,-0.263s4.322,12.244 -0.522,17.016c-3.917,3.858 -9.648,4.674 -14.108,2.4"
android:strokeWidth="2"
android:strokeColor="#000000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
</vector>

View File

@ -1,31 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="52"
android:viewportHeight="52">
<group
android:translateX="-10"
android:translateY="-10">
<path
android:pathData="M53,32.25l1.875,0l0,26.875l-38,0l0,-26.875l1.875,0z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M21.375,28.915c0,-8.379 6.415,-16.274 14.318,-16.523c7.97,-0.251 15.41,7.285 14.742,16.523"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M25.548,28.915c0,-6.335 4.576,-12.305 10.212,-12.493c5.684,-0.19 10.991,5.508 10.514,12.493"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"

View File

@ -14,7 +14,6 @@
android:padding="16dp"> android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/nameInputLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
@ -64,7 +63,6 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/formatInputLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -9,7 +9,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/fidelityList" android:id="@+id/fidelityList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:layout_margin="24dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
@ -25,7 +26,6 @@
app:srcCompat="@drawable/search" /> app:srcCompat="@drawable/search" />
<LinearLayout <LinearLayout
android:id="@+id/expandedMenuContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"

View File

@ -11,12 +11,26 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<com.google.android.material.textview.MaterialTextView <net.helcel.fidelity.activity.view.ScannerView
android:id="@+id/bottomText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="64dp" android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/btnScanDone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:background="#ffffff" android:layout_centerHorizontal="true"
android:textSize="24sp" /> android:layout_margin="24dp"
android:contentDescription="@string/manual" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_margin="28dp"
android:indeterminate="true" />
</RelativeLayout> </RelativeLayout>

View File

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textViewFeelings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="15dp" android:padding="15dp"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="kp2aplugin_title" tools:keep="@string/kp2aplugin_title">Fidelity</string> <string name="kp2aplugin_title" tools:keep="@string/kp2aplugin_title">Fidelity</string>
<string name="kp2aplugin_shortdesc">Stores and Displays fidelity and other cards</string> <string name="kp2aplugin_shortdesc" tools:keep="@string/kp2aplugin_shortdesc">Fidelity adds an interface to manage fidelity cards and other barcodes to Keepass2Android</string>
<string name="kp2aplugin_author" tools:keep="@string/kp2aplugin_author">[soraefir](soraefir)</string> <string name="kp2aplugin_author" tools:keep="@string/kp2aplugin_author">Soraefir</string>
<string name="app_name">Keepass Fidelity</string> <string name="app_name">Keepass Fidelity</string>

View File

@ -1,3 +0,0 @@
<resources>
</resources>

View File

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.Fidelity" parent="Theme.Material3.DayNight.NoActionBar"> <style name="Theme.Fidelity" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">?attr/colorAccent</item>
<item name="colorPrimary">#7DB9F5</item>
<item name="colorPrimaryVariant">#7DB9F5</item>
<item name="colorSecondary">#7DB9F5</item>
<item name="colorSecondaryVariant">#7DB9F5</item>
<item name="colorOnPrimary">#030B12</item>
</style> </style>
</resources> </resources>

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id 'com.android.application' version '8.3.0' apply false id 'com.android.application' version '8.3.1' apply false
id 'com.android.library' version '8.3.0' apply false id 'com.android.library' version '8.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
} }

View File

@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# Android operating system, and which are packaged with your app's APK # Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=false
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the # Enables namespacing of each library's R class so that its R class includes only the