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)) { | ||||
|         if (s.isNullOrEmpty()) return strings | ||||
|  | ||||
|         try { | ||||
|             val a = JSONArray(s) | ||||
|                 for (i in 0 until a.length()) { | ||||
|                     val url = a.optString(i) | ||||
|                     strings.add(url) | ||||
|                 } | ||||
|             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( | ||||
|         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) { | ||||
|         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)) | ||||
|         } else if (action == Strings.ACTION_ENTRY_OUTPUT_MODIFIED) { | ||||
|  | ||||
|             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 | ||||
|  | ||||
|             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] | ||||
|         ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user