Update plugin com.android.application to v8.3.1 #3
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
@ -21,32 +20,28 @@
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".pluginSDK.PluginAccessReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
android:name=".pluginSDK.PluginAccessBroadcastReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
||||
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".pluginSDK.PluginActionBroadcastReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<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_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>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -18,6 +18,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActMainBinding
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedPreferences =
|
||||
|
@ -26,7 +26,7 @@ class CreateEntry : Fragment() {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private lateinit var binding: FragCreateEntryBinding
|
||||
|
||||
private val resultLauncherAdd = KeepassWrapper.resultLauncherAdd(this) {
|
||||
private val resultLauncherAdd = KeepassWrapper.resultLauncher(this) {
|
||||
val r = KeepassWrapper.entryExtract(it)
|
||||
if (!KeepassWrapper.isProtected(it)) {
|
||||
CacheManager.addFidelity(r)
|
||||
|
@ -23,7 +23,7 @@ class Launcher : Fragment() {
|
||||
private lateinit var binding: FragLauncherBinding
|
||||
private lateinit var fidelityListAdapter: FidelityListAdapter
|
||||
|
||||
private val resultLauncherQuery = KeepassWrapper.resultLauncherQuery(this) {
|
||||
private val resultLauncherQuery = KeepassWrapper.resultLauncher(this) {
|
||||
val r = KeepassWrapper.entryExtract(it)
|
||||
if (!KeepassWrapper.isProtected(it)) {
|
||||
CacheManager.addFidelity(r)
|
||||
@ -80,7 +80,7 @@ class Launcher : Fragment() {
|
||||
|
||||
private fun startGetFromKeepass() {
|
||||
try {
|
||||
this.resultLauncherQuery.launch(Kp2aControl.queryEntryIntentForOwnPackage)
|
||||
this.resultLauncherQuery.launch(Kp2aControl.getQueryEntryForOwnPackageIntent())
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
ErrorToaster.noKP2AFound(requireActivity())
|
||||
}
|
||||
|
@ -2,141 +2,87 @@ package net.helcel.fidelity.pluginSDK
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
|
||||
|
||||
class PluginAccessException(msg: String) : Exception(msg)
|
||||
|
||||
|
||||
object AccessManager {
|
||||
private const val _tag = "Kp2aPluginSDK"
|
||||
private const val PREF_KEY_SCOPE = "scope"
|
||||
private const val PREF_KEY_TOKEN = "token"
|
||||
|
||||
private fun stringArrayToString(values: ArrayList<String?>): String? {
|
||||
if (values.isEmpty()) return null
|
||||
val a = JSONArray()
|
||||
for (i in values.indices) {
|
||||
a.put(values[i])
|
||||
}
|
||||
return if (values.isNotEmpty()) {
|
||||
a.toString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
values.forEach { a.put(it) }
|
||||
return a.toString()
|
||||
}
|
||||
|
||||
private fun stringToStringArray(s: String?): ArrayList<String> {
|
||||
val strings = ArrayList<String>()
|
||||
if (!TextUtils.isEmpty(s)) {
|
||||
try {
|
||||
val a = JSONArray(s)
|
||||
for (i in 0 until a.length()) {
|
||||
val url = a.optString(i)
|
||||
strings.add(url)
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
if (s.isNullOrEmpty()) return strings
|
||||
|
||||
try {
|
||||
val a = JSONArray(s)
|
||||
for (i in 0 until a.length())
|
||||
strings.add(a.optString(i))
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return strings
|
||||
}
|
||||
|
||||
fun storeAccessToken(
|
||||
ctx: Context,
|
||||
hostPackage: String,
|
||||
accessToken: String,
|
||||
hostPackage: String?,
|
||||
accessToken: String?,
|
||||
scopes: ArrayList<String?>
|
||||
) {
|
||||
val prefs = getPrefsForHost(ctx, hostPackage)
|
||||
|
||||
val edit = prefs.edit()
|
||||
edit.putString(PREF_KEY_TOKEN, accessToken)
|
||||
val scopesString = stringArrayToString(scopes)
|
||||
edit.putString(PREF_KEY_SCOPE, scopesString)
|
||||
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)
|
||||
if (!hostPrefs.contains(hostPackage)) {
|
||||
if (!hostPrefs.contains(hostPackage))
|
||||
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(
|
||||
ctx: Context,
|
||||
hostPackage: String
|
||||
hostPackage: String?
|
||||
): SharedPreferences {
|
||||
val prefs = ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE)
|
||||
return prefs
|
||||
return ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
fun tryGetAccessToken(ctx: Context, hostPackage: String, scopes: ArrayList<String?>): String? {
|
||||
if (TextUtils.isEmpty(hostPackage)) {
|
||||
Log.d(_tag, "hostPackage is empty!")
|
||||
return null
|
||||
}
|
||||
Log.d(_tag, "trying to find prefs for $hostPackage")
|
||||
fun tryGetAccessToken(ctx: Context, hostPackage: String?, scopes: ArrayList<String?>): String? {
|
||||
if (hostPackage.isNullOrEmpty()) return null
|
||||
|
||||
val prefs = getPrefsForHost(ctx, hostPackage)
|
||||
val scopesString = prefs.getString(PREF_KEY_SCOPE, "")
|
||||
Log.d(_tag, "available scopes: $scopesString")
|
||||
val currentScope = stringToStringArray(scopesString)
|
||||
if (isSubset(scopes, currentScope)) {
|
||||
return prefs.getString(PREF_KEY_TOKEN, null)
|
||||
} else {
|
||||
Log.d(_tag, "looks like scope changed. Access token invalid.")
|
||||
if (!isSubset(scopes, currentScope))
|
||||
return null
|
||||
}
|
||||
return prefs.getString(PREF_KEY_TOKEN, null)
|
||||
|
||||
}
|
||||
|
||||
private fun isSubset(
|
||||
requiredScopes: ArrayList<String?>,
|
||||
availableScopes: ArrayList<String>
|
||||
): Boolean {
|
||||
for (r in requiredScopes) {
|
||||
if (availableScopes.indexOf(r) < 0) {
|
||||
Log.d(_tag, "Scope " + r + " not available. " + availableScopes.size)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return availableScopes.containsAll(requiredScopes)
|
||||
}
|
||||
|
||||
fun removeAccessToken(
|
||||
ctx: Context, hostPackage: String,
|
||||
accessToken: String
|
||||
ctx: Context, hostPackage: String?,
|
||||
accessToken: String?
|
||||
) {
|
||||
val prefs = getPrefsForHost(ctx, hostPackage)
|
||||
|
||||
Log.d(_tag, "removing AccessToken.")
|
||||
if (prefs.getString(PREF_KEY_TOKEN, "") == accessToken) {
|
||||
val edit = prefs.edit()
|
||||
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(
|
||||
context: Context, hostPackage: String,
|
||||
context: Context, hostPackage: String?,
|
||||
scopes: ArrayList<String?>
|
||||
): String {
|
||||
val accessToken = tryGetAccessToken(context, hostPackage, scopes)
|
||||
?: throw PluginAccessException(hostPackage, scopes)
|
||||
return accessToken
|
||||
return tryGetAccessToken(context, hostPackage, scopes)
|
||||
?: throw PluginAccessException(hostPackage + scopes)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -1,96 +1,38 @@
|
||||
package net.helcel.fidelity.pluginSDK
|
||||
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
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(
|
||||
outputData: String?,
|
||||
fun getAddEntryIntent(
|
||||
fields: HashMap<String?, String?>,
|
||||
protectedFields: ArrayList<String?>?
|
||||
): Intent {
|
||||
val outputData = JSONObject((fields as Map<*, *>)).toString()
|
||||
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", "CreateEntryThenCloseTask")
|
||||
startKp2aIntent.putExtra("ShowUserNotifications", "false") //KP2A expects a StringExtra
|
||||
startKp2aIntent.putExtra("ShowUserNotifications", "false")
|
||||
startKp2aIntent.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, outputData)
|
||||
if (protectedFields != null) startKp2aIntent.putStringArrayListExtra(
|
||||
Strings.EXTRA_PROTECTED_FIELDS_LIST,
|
||||
protectedFields
|
||||
)
|
||||
|
||||
|
||||
if (protectedFields != null)
|
||||
startKp2aIntent.putStringArrayListExtra(
|
||||
Strings.EXTRA_PROTECTED_FIELDS_LIST,
|
||||
protectedFields
|
||||
)
|
||||
return startKp2aIntent
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 getQueryEntryForOwnPackageIntent(): Intent {
|
||||
return Intent(Strings.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
fun getEntryFieldsFromIntent(intent: Intent?): HashMap<String, String> {
|
||||
val res = HashMap<String, String>()
|
||||
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()
|
||||
while (iter.hasNext()) {
|
||||
val key = iter.next()
|
||||
|
@ -3,55 +3,29 @@ package net.helcel.fidelity.pluginSDK
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
class PluginAccessBroadcastReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
Log.d(_tag, "received broadcast with action=$action")
|
||||
if (action == null) return
|
||||
val action = intent.action ?: return
|
||||
when (action) {
|
||||
Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent)
|
||||
Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent)
|
||||
Strings.ACTION_REVOKE_ACCESS -> revokeAccess(ctx, intent)
|
||||
else -> {}
|
||||
else -> println(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun revokeAccess(ctx: Context, intent: Intent) {
|
||||
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
|
||||
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) {
|
||||
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
|
||||
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) {
|
||||
@ -62,21 +36,18 @@ abstract class PluginAccessBroadcastReceiver : BroadcastReceiver() {
|
||||
rpi.putExtra(Strings.EXTRA_SENDER, ctx.packageName)
|
||||
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.putStringArrayListExtra(Strings.EXTRA_SCOPES, scopes)
|
||||
Log.d(_tag, "requesting access for " + scopes.size + " tokens.")
|
||||
ctx.sendBroadcast(rpi)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the list of required scopes for this plugin.
|
||||
*/
|
||||
abstract val scopes: ArrayList<String?>
|
||||
|
||||
companion object {
|
||||
private const val _tag = "Kp2aPluginSDK"
|
||||
}
|
||||
private val scopes: ArrayList<String?> = ArrayList(
|
||||
listOf(
|
||||
Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE,
|
||||
Strings.SCOPE_DATABASE_ACTIONS,
|
||||
Strings.SCOPE_CURRENT_ENTRY,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
|
||||
get() {
|
||||
val res = HashMap<String, String>()
|
||||
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()
|
||||
while (iter.hasNext()) {
|
||||
val key = iter.next()
|
||||
@ -54,14 +55,13 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
|
||||
get() = _intent.getStringExtra(Strings.EXTRA_ENTRY_ID)
|
||||
|
||||
|
||||
@Throws(PluginAccessException::class)
|
||||
fun setEntryField(fieldId: String?, fieldValue: String?, isProtected: Boolean) {
|
||||
val i = Intent(Strings.ACTION_SET_ENTRY_FIELD)
|
||||
val scope = ArrayList<String?>()
|
||||
scope.add(Strings.SCOPE_CURRENT_ENTRY)
|
||||
i.putExtra(
|
||||
Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(
|
||||
context, hostPackage!!, scope
|
||||
context, hostPackage, scope
|
||||
)
|
||||
)
|
||||
i.setPackage(hostPackage)
|
||||
@ -132,7 +132,6 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
|
||||
*/
|
||||
get() = protectedFieldsListFromIntent
|
||||
|
||||
@Throws(PluginAccessException::class)
|
||||
fun addEntryAction(
|
||||
actionDisplayText: String?,
|
||||
actionIconResourceId: Int,
|
||||
@ -141,7 +140,6 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
|
||||
addEntryFieldAction(null, null, actionDisplayText, actionIconResourceId, actionData)
|
||||
}
|
||||
|
||||
@Throws(PluginAccessException::class)
|
||||
fun addEntryFieldAction(
|
||||
actionId: String?,
|
||||
fieldId: String?,
|
||||
@ -191,24 +189,28 @@ class PluginActionBroadcastReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
override fun onReceive(ctx: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
val action = intent.action ?: return
|
||||
Log.d(
|
||||
"KP2A.pluginsdk",
|
||||
"received broadcast in PluginActionBroadcastReceiver with action=$action"
|
||||
)
|
||||
if (action == null) return
|
||||
if (action == Strings.ACTION_OPEN_ENTRY) {
|
||||
openEntry(OpenEntryAction(ctx, intent))
|
||||
} else if (action == Strings.ACTION_CLOSE_ENTRY_VIEW) {
|
||||
closeEntryView(CloseEntryViewAction(ctx, intent))
|
||||
} else if (action == Strings.ACTION_ENTRY_ACTION_SELECTED) {
|
||||
actionSelected(ActionSelectedAction(ctx, intent))
|
||||
} else if (action == Strings.ACTION_ENTRY_OUTPUT_MODIFIED) {
|
||||
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))
|
||||
} else {
|
||||
//TODO handle unexpected action
|
||||
println(action)
|
||||
|
||||
when (action) {
|
||||
Strings.ACTION_OPEN_ENTRY -> openEntry(OpenEntryAction(ctx, intent))
|
||||
Strings.ACTION_CLOSE_ENTRY_VIEW -> closeEntryView(CloseEntryViewAction(ctx, intent))
|
||||
Strings.ACTION_ENTRY_ACTION_SELECTED ->
|
||||
actionSelected(ActionSelectedAction(ctx, intent))
|
||||
|
||||
Strings.ACTION_ENTRY_OUTPUT_MODIFIED ->
|
||||
entryOutputModified(EntryOutputModifiedAction(ctx, intent))
|
||||
|
||||
Strings.ACTION_LOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
||||
Strings.ACTION_UNLOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
||||
Strings.ACTION_OPEN_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
||||
Strings.ACTION_CLOSE_DATABASE -> dbAction(DatabaseAction(ctx, intent))
|
||||
|
||||
else -> println(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,6 @@ object Strings {
|
||||
const val 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
|
||||
*/
|
||||
@ -97,11 +92,6 @@ object Strings {
|
||||
*/
|
||||
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)
|
||||
@ -157,13 +147,6 @@ object Strings {
|
||||
const val 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.
|
||||
* 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_PROTECTED = "keepass2android.EXTRA_FIELD_PROTECTED"
|
||||
|
||||
const val PREFIX_STRING = "STRING_"
|
||||
const val PREFIX_BINARY = "BINARY_"
|
||||
|
||||
}
|
||||
|
@ -19,14 +19,17 @@ object BarcodeGenerator {
|
||||
android.graphics.Color.WHITE
|
||||
}
|
||||
|
||||
fun generateBarcode(content: String?, f: String?, width: Int): Bitmap? {
|
||||
fun generateBarcode(content: String?, f: String?, w: Int): Bitmap? {
|
||||
if (content.isNullOrEmpty() || f.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val format = stringToFormat(f)
|
||||
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 bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import android.os.Bundle
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.Fragment
|
||||
import net.helcel.fidelity.pluginSDK.KeepassDefs
|
||||
import net.helcel.fidelity.pluginSDK.KeepassDef
|
||||
import net.helcel.fidelity.pluginSDK.Kp2aControl
|
||||
|
||||
object KeepassWrapper {
|
||||
@ -25,8 +25,8 @@ object KeepassWrapper {
|
||||
|
||||
val fields = HashMap<String?, String?>()
|
||||
val protected = ArrayList<String?>()
|
||||
fields[KeepassDefs.TitleField] = title
|
||||
fields[KeepassDefs.UrlField] =
|
||||
fields[KeepassDef.TitleField] = title
|
||||
fields[KeepassDef.UrlField] =
|
||||
"androidapp://" + fragment.requireActivity().packageName
|
||||
fields[CODE_FIELD] = code
|
||||
fields[FORMAT_FIELD] = format
|
||||
@ -37,26 +37,14 @@ object KeepassWrapper {
|
||||
}
|
||||
|
||||
|
||||
fun resultLauncherAdd(
|
||||
fun resultLauncher(
|
||||
fragment: Fragment,
|
||||
callback: (HashMap<String, String>) -> Unit
|
||||
): ActivityResultLauncher<Intent> {
|
||||
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
|
||||
println(result.toString())
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val credentials = Kp2aControl.getEntryFieldsFromIntent(result.data)
|
||||
println(credentials.toList().toString())
|
||||
callback(credentials)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resultLauncherQuery(
|
||||
fragment: Fragment,
|
||||
callback: (HashMap<String, String>) -> Unit
|
||||
): ActivityResultLauncher<Intent> {
|
||||
return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
println(result.resultCode)
|
||||
println(result.data.toString())
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val credentials = Kp2aControl.getEntryFieldsFromIntent(result.data)
|
||||
println(credentials.toList().toString())
|
||||
@ -67,7 +55,7 @@ object KeepassWrapper {
|
||||
|
||||
fun entryExtract(map: HashMap<String, String>): Triple<String?, String?, String?> {
|
||||
return Triple(
|
||||
map[KeepassDefs.TitleField],
|
||||
map[KeepassDef.TitleField],
|
||||
map[CODE_FIELD],
|
||||
map[FORMAT_FIELD]
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user