mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-11 17:03:33 -04:00
Refactor (#846)
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
|
||||
<service android:name=".AutofillService" android:permission="android.permission.BIND_AUTOFILL_SERVICE" android:exported="true">
|
||||
<service android:name=".autofill.AutofillService" android:permission="android.permission.BIND_AUTOFILL_SERVICE" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.autofill.AutofillService"/>
|
||||
</intent-filter>
|
||||
|
||||
@@ -5,25 +5,9 @@
|
||||
* to forms. It identifies username and password fields in apps and websites,
|
||||
* then offers stored credentials from AliasVault.
|
||||
*
|
||||
* IMPORTANT IMPLEMENTATION NOTES:
|
||||
* 1. Since autofill services don't have direct access to activities, we need a way to
|
||||
* authenticate the user. The current implementation:
|
||||
* - Shows a Toast indicating authentication is needed
|
||||
* - In a real implementation, would launch an activity for authentication
|
||||
*
|
||||
* 2. To complete this implementation, you need to:
|
||||
* - Register this service in AndroidManifest.xml with proper metadata
|
||||
* - Add a way to communicate between the launched activity and this service
|
||||
* - Implement credential storage/retrieval with proper authentication
|
||||
*
|
||||
* 3. For full production implementation, consider:
|
||||
* - Adding a specific autofill activity for authentication
|
||||
* - Implementing dataset presentation customization
|
||||
* - Adding support for save functionality
|
||||
* - Implementing field detection heuristics for apps without autofill hints
|
||||
*/
|
||||
package net.aliasvault.app
|
||||
import android.app.assist.AssistStructure
|
||||
package net.aliasvault.app.autofill
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.CancellationSignal
|
||||
import android.service.autofill.AutofillService
|
||||
@@ -34,18 +18,15 @@ import android.service.autofill.FillResponse
|
||||
import android.service.autofill.SaveCallback
|
||||
import android.service.autofill.SaveRequest
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.autofill.AutofillId
|
||||
import android.view.autofill.AutofillValue
|
||||
import android.widget.RemoteViews
|
||||
import net.aliasvault.app.vaultstore.VaultStore
|
||||
import net.aliasvault.app.vaultstore.VaultStore.CredentialOperationCallback
|
||||
import net.aliasvault.app.vaultstore.models.Credential
|
||||
import net.aliasvault.app.autofill.CredentialMatcher
|
||||
import androidx.core.net.toUri
|
||||
import android.app.PendingIntent
|
||||
import net.aliasvault.app.autofill.FieldFinder
|
||||
import net.aliasvault.app.autofill.ImageUtils
|
||||
import net.aliasvault.app.MainActivity
|
||||
import net.aliasvault.app.R
|
||||
import net.aliasvault.app.autofill.utils.*
|
||||
import net.aliasvault.app.autofill.models.FieldType
|
||||
|
||||
class AutofillService : AutofillService() {
|
||||
@@ -104,8 +85,8 @@ class AutofillService : AutofillService() {
|
||||
private fun launchActivityForAutofill(fieldFinder: FieldFinder, callback: FillCallback) {
|
||||
Log.d(TAG, "Launching activity for autofill authentication")
|
||||
|
||||
// Get the app/website information from the structure
|
||||
val appInfo = getAppInfo(fieldFinder.structure)
|
||||
// Get the app/website information from assist structure.
|
||||
val appInfo = fieldFinder.getAppInfo()
|
||||
Log.d(TAG, "Autofill request from: $appInfo")
|
||||
|
||||
// Ignore requests from our own unlock page as this would cause a loop
|
||||
@@ -183,96 +164,6 @@ class AutofillService : AutofillService() {
|
||||
callback.onSuccess(responseBuilder.build())
|
||||
}
|
||||
|
||||
private fun getAppInfo(structure: AssistStructure?): String? {
|
||||
if (structure == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// First check if this is web content
|
||||
val nodeCount = structure.windowNodeCount
|
||||
for (i in 0 until nodeCount) {
|
||||
val windowNode = structure.getWindowNodeAt(i)
|
||||
val rootNode = windowNode.rootViewNode
|
||||
|
||||
// Check for web-specific information
|
||||
val webInfo = findWebInfoInNode(rootNode)
|
||||
if (webInfo != null) {
|
||||
Log.d(TAG, "Found web info: $webInfo")
|
||||
return webInfo
|
||||
}
|
||||
}
|
||||
|
||||
// If no web info found, fall back to package name
|
||||
val packageName = structure.activityComponent?.packageName
|
||||
if (packageName != null) {
|
||||
Log.d(TAG, "Using package name: $packageName")
|
||||
return packageName
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findWebInfoInNode(node: AssistStructure.ViewNode): String? {
|
||||
// Check for web domain
|
||||
val webDomain = node.webDomain
|
||||
val webScheme = node.webScheme
|
||||
if (webDomain != null && webScheme != null) {
|
||||
return "$webScheme://$webDomain"
|
||||
}
|
||||
|
||||
// Check for web URL
|
||||
val webUrl = node.webDomain
|
||||
if (webUrl != null) {
|
||||
try {
|
||||
val uri = webUrl.toUri()
|
||||
val host = uri.host
|
||||
if (host != null) {
|
||||
return host
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error parsing web URL: $webUrl", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Check HTML info for domain or URL
|
||||
val htmlInfo = node.htmlInfo
|
||||
if (htmlInfo != null) {
|
||||
val attributes = htmlInfo.attributes
|
||||
if (attributes != null) {
|
||||
for (i in 0 until attributes.size) {
|
||||
val name = attributes.get(i)?.first
|
||||
val value = attributes.get(i)?.second
|
||||
if (name == "domain" || name == "host" || name == "url") {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for web-specific hints
|
||||
val hints = node.autofillHints
|
||||
if (hints != null) {
|
||||
for (hint in hints) {
|
||||
if (hint.contains("web", ignoreCase = true) ||
|
||||
hint.contains("url", ignoreCase = true) ||
|
||||
hint.contains("domain", ignoreCase = true)) {
|
||||
return hint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check child nodes
|
||||
val childCount = node.childCount
|
||||
for (i in 0 until childCount) {
|
||||
val webInfo = findWebInfoInNode(node.getChildAt(i))
|
||||
if (webInfo != null) {
|
||||
return webInfo
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Helper method to create a dataset from a credential
|
||||
private fun createCredentialDataset(
|
||||
fieldFinder: FieldFinder,
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.aliasvault.app.autofill
|
||||
package net.aliasvault.app.autofill.utils
|
||||
|
||||
import net.aliasvault.app.vaultstore.models.Credential
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package net.aliasvault.app.autofill
|
||||
package net.aliasvault.app.autofill.utils
|
||||
|
||||
import android.app.assist.AssistStructure
|
||||
import android.view.View
|
||||
import android.view.autofill.AutofillId
|
||||
import android.util.Log
|
||||
import androidx.core.net.toUri
|
||||
import net.aliasvault.app.autofill.models.FieldType
|
||||
|
||||
/**
|
||||
* Helper class to find fields in the assist structure.
|
||||
*/
|
||||
class FieldFinder(var structure: AssistStructure) {
|
||||
private val TAG = "AliasVaultAutofill"
|
||||
|
||||
// Store pairs of (AutofillId, net.aliasvault.app.autofill.models.FieldType)
|
||||
val autofillableFields = mutableListOf<Pair<AutofillId, FieldType>>()
|
||||
var foundPasswordField = false
|
||||
@@ -24,6 +28,33 @@ class FieldFinder(var structure: AssistStructure) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current app or website information from the assist structure to know
|
||||
* what credential suggestions to show.
|
||||
*/
|
||||
fun getAppInfo(): String? {
|
||||
// First check if this is web content
|
||||
val nodeCount = structure.windowNodeCount
|
||||
for (i in 0 until nodeCount) {
|
||||
val windowNode = structure.getWindowNodeAt(i)
|
||||
val rootNode = windowNode.rootViewNode
|
||||
|
||||
// Check for web-specific information
|
||||
val webInfo = findWebInfoInNode(rootNode)
|
||||
if (webInfo != null) {
|
||||
return webInfo
|
||||
}
|
||||
}
|
||||
|
||||
// If no web info found, fall back to package name
|
||||
val packageName = structure.activityComponent?.packageName
|
||||
if (packageName != null) {
|
||||
return packageName
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a field is most likely an email field, username field, password field, or unknown.
|
||||
*/
|
||||
@@ -49,6 +80,70 @@ class FieldFinder(var structure: AssistStructure) {
|
||||
return FieldType.UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to find the web domain or URL in the assist structure.
|
||||
*/
|
||||
private fun findWebInfoInNode(node: AssistStructure.ViewNode): String? {
|
||||
// Check for web domain
|
||||
val webDomain = node.webDomain
|
||||
val webScheme = node.webScheme
|
||||
if (webDomain != null && webScheme != null) {
|
||||
return "$webScheme://$webDomain"
|
||||
}
|
||||
|
||||
// Check for web URL
|
||||
val webUrl = node.webDomain
|
||||
if (webUrl != null) {
|
||||
try {
|
||||
val uri = webUrl.toUri()
|
||||
val host = uri.host
|
||||
if (host != null) {
|
||||
return host
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error parsing web URL: $webUrl", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Check HTML info for domain or URL
|
||||
val htmlInfo = node.htmlInfo
|
||||
if (htmlInfo != null) {
|
||||
val attributes = htmlInfo.attributes
|
||||
if (attributes != null) {
|
||||
for (i in 0 until attributes.size) {
|
||||
val name = attributes.get(i)?.first
|
||||
val value = attributes.get(i)?.second
|
||||
if (name == "domain" || name == "host" || name == "url") {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for web-specific hints
|
||||
val hints = node.autofillHints
|
||||
if (hints != null) {
|
||||
for (hint in hints) {
|
||||
if (hint.contains("web", ignoreCase = true) ||
|
||||
hint.contains("url", ignoreCase = true) ||
|
||||
hint.contains("domain", ignoreCase = true)) {
|
||||
return hint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively check child nodes
|
||||
val childCount = node.childCount
|
||||
for (i in 0 until childCount) {
|
||||
val webInfo = findWebInfoInNode(node.getChildAt(i))
|
||||
if (webInfo != null) {
|
||||
return webInfo
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseNode(node: AssistStructure.ViewNode) {
|
||||
val viewId = node.autofillId
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.aliasvault.app.autofill
|
||||
package net.aliasvault.app.autofill.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
Reference in New Issue
Block a user