Update plugin com.android.application to v8.3.1 #3
| @@ -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> | ||||||
| @@ -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() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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" | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -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()) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -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())) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -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 | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -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 | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -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 { |  | ||||||
|                 val a = JSONArray(s) |         try { | ||||||
|                 for (i in 0 until a.length()) { |             val a = JSONArray(s) | ||||||
|                     val url = a.optString(i) |             for (i in 0 until a.length()) | ||||||
|                     strings.add(url) |                 strings.add(a.optString(i)) | ||||||
|                 } |         } 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 |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | 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) | ||||||
|             Strings.EXTRA_PROTECTED_FIELDS_LIST, |             startKp2aIntent.putStringArrayListExtra( | ||||||
|             protectedFields |                 Strings.EXTRA_PROTECTED_FIELDS_LIST, | ||||||
|         ) |                 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() | ||||||
|   | |||||||
| @@ -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" |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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() { |             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) { |  | ||||||
|             entryOutputModified(EntryOutputModifiedAction(ctx, intent)) |             Strings.ACTION_ENTRY_OUTPUT_MODIFIED -> | ||||||
|         } else if (action == Strings.ACTION_LOCK_DATABASE || action == Strings.ACTION_UNLOCK_DATABASE || action == Strings.ACTION_OPEN_DATABASE || action == Strings.ACTION_CLOSE_DATABASE) { |                 entryOutputModified(EntryOutputModifiedAction(ctx, intent)) | ||||||
|             dbAction(DatabaseAction(ctx, intent)) |  | ||||||
|         } else { |             Strings.ACTION_LOCK_DATABASE -> dbAction(DatabaseAction(ctx, intent)) | ||||||
|             //TODO handle unexpected action |             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 = |     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_" |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -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) | ||||||
| @@ -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] | ||||||
|         ) |         ) | ||||||
|   | |||||||
| @@ -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> |  | ||||||
| @@ -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> |  | ||||||
| @@ -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> |  | ||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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> | ||||||
| @@ -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" | ||||||
|   | |||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| <resources> |  | ||||||
|  |  | ||||||
| </resources> |  | ||||||
| @@ -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> | ||||||
| @@ -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 | ||||||
| } | } | ||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user