Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<activity android:name=".ui.SettingsActivity" />
<activity android:name=".ui.AboutUsActivity" />
<activity android:name=".ui.LicenseActivity" />
<activity android:name=".autofill.AutofillSettingsActivity" />

<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
Expand All @@ -41,6 +42,20 @@
android:name="autoStoreLocales"
android:value="true" />
</service>

<service
android:name=".autofill.PasswordAutofillService"
android:label="@string/app_name"
android:permission="android.permission.BIND_AUTOFILL_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>

<meta-data
android:name="android.autofill"
android:resource="@xml/autofill_service" />
</service>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.jeeldobariya.passcodes.autofill

import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import com.jeeldobariya.passcodes.R
import com.google.android.material.button.MaterialButton

class AutofillSettingsActivity : AppCompatActivity() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don;t understand what this is for... and where this activity will be call..

I mean, currently it not called from anywhere... It was probably intend to be call from setting activity.. I guess.


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_autofill_settings)

val enableAutofillButton = findViewById<MaterialButton>(R.id.enable_autofill_button)
enableAutofillButton.setOnClickListener {
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
intent.data = android.net.Uri.parse("package:$packageName")
startActivity(intent)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.jeeldobariya.passcodes.autofill

import android.app.assist.AssistStructure
import android.os.CancellationSignal
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.FillCallback
import android.service.autofill.FillContext
import android.service.autofill.FillRequest
import android.service.autofill.FillResponse
import android.service.autofill.SaveCallback
import android.service.autofill.SaveRequest
import android.view.autofill.AutofillValue
import android.widget.RemoteViews
import com.jeeldobariya.passcodes.R
import com.jeeldobariya.passcodes.data.Passcode
import com.jeeldobariya.passcodes.data.PasscodeDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch

class PasswordAutofillService : AutofillService() {

private val serviceScope = CoroutineScope(Dispatchers.IO)

override fun onFillRequest(
request: FillRequest,
cancellationSignal: CancellationSignal,
callback: FillCallback
) {
val context = request.fillContexts
val structure = context.last().structure

val viewNodes = mutableMapOf<String, AssistStructure.ViewNode>()
parseStructure(structure.getWindowNodeAt(0).rootViewNode, viewNodes)

val usernameNode = viewNodes["username"] ?: viewNodes["emailAddress"]
val passwordNode = viewNodes["password"]

if (usernameNode?.autofillId == null || passwordNode?.autofillId == null) {
callback.onSuccess(null)
return
}

val usernameId = usernameNode.autofillId!!
val passwordId = passwordNode.autofillId!!

cancellationSignal.setOnCancelListener {
// Handle cancellation
}

serviceScope.launch {
val passcodes = PasscodeDatabase.getDatabase(applicationContext).passcodeDao().getAllPasscodes().first()
val responseBuilder = FillResponse.Builder()

for (passcode in passcodes) {
val presentation = RemoteViews(packageName, R.layout.autofill_list_item).apply {
setTextViewText(R.id.autofill_username, passcode.name)
}

val dataset = android.service.autofill.Dataset.Builder(presentation)
.setValue(usernameId, AutofillValue.forText(passcode.name))
.setValue(passwordId, AutofillValue.forText(passcode.value))
.build()
responseBuilder.addDataset(dataset)
}

callback.onSuccess(responseBuilder.build())
}
}

override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
val context = request.fillContexts
val structure = context.last().structure

val viewNodes = mutableMapOf<String, AssistStructure.ViewNode>()
parseStructure(structure.getWindowNodeAt(0).rootViewNode, viewNodes)

val usernameNode = viewNodes["username"] ?: viewNodes["emailAddress"]
val passwordNode = viewNodes["password"]

val username = usernameNode?.text?.toString()
val password = passwordNode?.text?.toString()

if (!username.isNullOrEmpty() && !password.isNullOrEmpty()) {
serviceScope.launch {
PasscodeDatabase.getDatabase(applicationContext).passcodeDao().insert(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the existing database. because app is in production and we can;t effort to lose users data.

refer:- Controller.kt interact with this class instead of master db directly

Passcode(name = username, value = password)
)
}
callback.onSuccess()
} else {
callback.onFailure("Could not save credentials.")
}
}

private fun parseStructure(
node: AssistStructure.ViewNode,
viewNodes: MutableMap<String, AssistStructure.ViewNode>
) {
node.autofillHints?.forEach { hint ->
if (!viewNodes.containsKey(hint)) {
viewNodes[hint] = node
}
}

for (i in 0 until node.childCount) {
parseStructure(node.getChildAt(i), viewNodes)
}
}
}
12 changes: 12 additions & 0 deletions app/src/main/kotlin/com/jeeldobariya/passcodes/data/Passcode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.jeeldobariya.passcodes.data

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "passcodes")
data class Passcode(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val name: String,
val value: String
)
28 changes: 28 additions & 0 deletions app/src/main/kotlin/com/jeeldobariya/passcodes/data/PasscodeDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.jeeldobariya.passcodes.data

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow

@Dao
interface PasscodeDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(passcode: Passcode)

@Update
suspend fun update(passcode: Passcode)

@Delete
suspend fun delete(passcode: Passcode)

@Query("SELECT * FROM passcodes ORDER BY name ASC")
fun getAllPasscodes(): Flow<List<Passcode>>

@Query("SELECT * FROM passcodes WHERE id = :id")
fun getPasscode(id: Int): Flow<Passcode>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.jeeldobariya.passcodes.data

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Passcode::class], version = 1, exportSchema = false)
abstract class PasscodeDatabase : RoomDatabase() {

abstract fun passcodeDao(): PasscodeDao

companion object {
@Volatile
private var INSTANCE: PasscodeDatabase? = null

fun getDatabase(context: Context): PasscodeDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PasscodeDatabase::class.java,
"passcode_database"
).build()
INSTANCE = instance
instance
}
}
}
}
29 changes: 29 additions & 0 deletions app/src/main/res/layout/activity_autofill_settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enable Passcodes Autofill"
android:textAppearance="@style/TextAppearance.Material3.TitleLarge" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="To use Passcodes as your password manager, you need to enable it as an autofill service in your device settings."
android:textAlignment="center" />

<com.google.android.material.button.MaterialButton
android:id="@+id/enable_autofill_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Open Settings" />

</LinearLayout>
10 changes: 5 additions & 5 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
android:layout_width="128dp"
android:layout_height="128dp"
android:background="@drawable/ic_passodes"
android:contentDescription="Passcodes Logo" />
android:contentDescription="@string/passcodes_logo" />

<LinearLayout
android:layout_width="wrap_content"
Expand All @@ -20,15 +20,15 @@
android:orientation="vertical"
android:layout_margin="16sp">

<com.google.android.material.textview.MaterialTextView
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/app_name"
android:textAlignment="center"
android:textSize="32dp" />
<com.google.android.material.textview.MaterialTextView

<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
Expand All @@ -50,7 +50,7 @@
android:layout_height="wrap_content"
android:text="@string/password_manager_button_text"
android:textSize="14dp" />

<com.google.android.material.button.MaterialButton
android:id="@+id/setting_btn"
style="@style/Widget.Material3.Button.OutlinedButton"
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/res/layout/autofill_list_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">

<TextView
android:id="@+id/autofill_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold" />

</LinearLayout>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,5 @@
<string name="danger_copy_to_clipboard_desc">Copying sensitive data like passwords to clipboard is not so good for security!!!</string>
<string name="confirm_dialog_button_text">Confirm</string>
<string name="discard_dialog_button_text">Discard</string>
<string name="passcodes_logo">Passcodes Logo</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/xml/autofill_service.xml
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don;t know why we require this file... I have seen the same over on keypass codebase..

What i mean is, if it just a empty file then, why we need this and what it is use for please explain.. if it use for configuration then how?

(I am new to android so don;t mind... I think it is require by android.. but i don't know it purpose..)

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.jeeldobariya.passcodes.autofill.AutofillSettingsActivity" />

2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ coroutines = "1.10.2"
lifecycle = "2.9.2"

# Plugin versions
agp = "8.11.0"
agp = "8.13.0"
kotlin-plugin = "2.1.21"
ksp = "2.1.21-2.0.2"
oss-license-plugin = "0.10.6" # latest safe version for oss-licenses-plugin
Expand Down