Skip to content

Commit 36a5fee

Browse files
committed
Shared passwords support
Thank you Codex for the UI
1 parent 3c56e2f commit 36a5fee

File tree

28 files changed

+8915
-1664
lines changed

28 files changed

+8915
-1664
lines changed

.dart_tool/build/fcd1995bc647fb959e82ea360c6c2c9a/asset_graph.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

android/app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
<service android:name=".services.credentials.CredentialService"
155155
android:enabled="true"
156156
android:exported="true"
157-
android:label="iCloud Passwords"
157+
android:label="iCloud Keychain"
158158
android:icon="@mipmap/ic_launcher"
159159
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
160160
tools:targetApi="upside_down_cake">
@@ -168,7 +168,7 @@
168168

169169
<service
170170
android:name=".services.credentials.OBAutofillService"
171-
android:label="iCloud Passwords"
171+
android:label="iCloud Keychain"
172172
android:exported="true"
173173
android:permission="android.permission.BIND_AUTOFILL_SERVICE"
174174
tools:targetApi="27">

android/app/src/main/kotlin/com/bluebubbles/messaging/services/credentials/CredentialCreateActivity.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.bluebubbles.messaging.services.credentials
22

3+
import android.content.Context
34
import android.content.Intent
45
import android.os.Build
56
import android.os.Bundle
@@ -29,6 +30,7 @@ import java.security.spec.ECGenParameterSpec
2930
import java.util.UUID
3031
import org.json.JSONObject
3132
import uniffi.rust_lib_bluebubbles.SavedPassword
33+
import androidx.core.content.edit
3234

3335
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
3436
class CredentialCreateActivity : FragmentActivity() {
@@ -73,6 +75,17 @@ class CredentialCreateActivity : FragmentActivity() {
7375
}
7476
}
7577

78+
private fun updateLastUsed() {
79+
val groupId = intent.getStringExtra("group_id")
80+
val prefs = getSharedPreferences("credential_usage_stats", Context.MODE_PRIVATE)
81+
val now = System.currentTimeMillis()
82+
if (groupId == null) {
83+
prefs.edit { putLong("usage_last_null", now) }
84+
} else {
85+
prefs.edit { putLong("usage_last_group_$groupId", now) }
86+
}
87+
}
88+
7689
fun handleService(service: NativePushState) {
7790
val request = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
7891
if (request == null) {
@@ -181,6 +194,8 @@ class CredentialCreateActivity : FragmentActivity() {
181194
return
182195
}
183196

197+
updateLastUsed()
198+
184199
// Set the CreateCredentialResponse as the result of the Activity
185200
PendingIntentHandler.setCreateCredentialResponse(
186201
result,
@@ -189,7 +204,7 @@ class CredentialCreateActivity : FragmentActivity() {
189204
setResult(RESULT_OK, result)
190205
finish()
191206
}
192-
})
207+
}, intent.getStringExtra("group_id"))
193208
}
194209
})
195210
} else if (request.callingRequest is CreatePasswordRequest) {
@@ -207,14 +222,17 @@ class CredentialCreateActivity : FragmentActivity() {
207222
return
208223
}
209224

225+
updateLastUsed()
226+
210227
PendingIntentHandler.setCreateCredentialResponse(
211228
result,
212229
CreatePasswordResponse()
213230
)
214231
setResult(RESULT_OK, result)
215232
finish()
216233
}
217-
}
234+
},
235+
intent.getStringExtra("group_id")
218236
)
219237
}
220238
}

android/app/src/main/kotlin/com/bluebubbles/messaging/services/credentials/CredentialService.kt

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ import com.bluebubbles.messaging.MainActivity
3232
import com.bluebubbles.messaging.services.rustpush.APNClient
3333
import com.bluebubbles.messaging.services.rustpush.APNService
3434
import org.json.JSONObject
35+
import uniffi.rust_lib_bluebubbles.AvailableGroupsCallback
3536
import uniffi.rust_lib_bluebubbles.RetrieveKeysCallback
3637
import uniffi.rust_lib_bluebubbles.SavedPassword
3738
import uniffi.rust_lib_bluebubbles.SavedPasskey
3839
import java.security.MessageDigest
40+
import java.time.Instant
3941

4042
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
4143
class CredentialService : CredentialProviderService() {
42-
val ICLOUD_ACCOUNT_ID = "OpenBubbles-iCloud"
4344

4445
override fun onBeginCreateCredentialRequest(
4546
request: BeginCreateCredentialRequest,
@@ -54,18 +55,54 @@ class CredentialService : CredentialProviderService() {
5455
return@ensurePrivilegedAllowlistFresh
5556
}
5657

58+
val prefs = getSharedPreferences("credential_usage_stats", Context.MODE_PRIVATE)
59+
5760
val intent = Intent(this, CredentialCreateActivity::class.java)
5861
val pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE
5962
or PendingIntent.FLAG_UPDATE_CURRENT)
6063

61-
val createEntries = listOf(
62-
CreateEntry(
63-
ICLOUD_ACCOUNT_ID,
64-
pending
64+
val client = APNClient(this)
65+
client.bind { service: APNService ->
66+
val push = service.pushState
67+
68+
// 1 to always put this first by default
69+
val lastUsedNull = prefs.getLong("usage_last_null", 1)
70+
val createEntries = mutableListOf(
71+
CreateEntry(
72+
accountName = "Not shared",
73+
pendingIntent = pending,
74+
description = null,
75+
lastUsedTime = if (lastUsedNull > 0) Instant.ofEpochMilli(lastUsedNull) else null
76+
)
6577
)
66-
)
6778

68-
callback.onResult(BeginCreateCredentialResponse(createEntries))
79+
if (push == null) {
80+
client.destroy()
81+
callback.onResult(BeginCreateCredentialResponse(createEntries))
82+
return@bind
83+
}
84+
85+
push.getAvailableGroups(object : AvailableGroupsCallback {
86+
override fun groups(groups: Map<String, String>) {
87+
createEntries.addAll(groups.asSequence().mapIndexed { idx, group ->
88+
val intent = Intent(this@CredentialService, CredentialCreateActivity::class.java)
89+
intent.putExtra("group_id", group.value)
90+
val pending = PendingIntent.getActivity(this@CredentialService, idx + 1, intent, PendingIntent.FLAG_MUTABLE
91+
or PendingIntent.FLAG_UPDATE_CURRENT)
92+
93+
val lastUsed = prefs.getLong("usage_last_group_${group.value}", 0)
94+
CreateEntry(
95+
accountName = group.key,
96+
pendingIntent = pending,
97+
description = "Saving to ${group.key}",
98+
lastUsedTime = if (lastUsed > 0) Instant.ofEpochMilli(lastUsed) else null
99+
)
100+
})
101+
callback.onResult(BeginCreateCredentialResponse(createEntries))
102+
}
103+
})
104+
}
105+
69106
}
70107
}
71108

@@ -256,4 +293,4 @@ class CredentialService : CredentialProviderService() {
256293
return "android:apk-key-hash:${Base64.encodeToString(certHash, Base64.NO_WRAP)}"
257294
}
258295
}
259-
}
296+
}

android/app/src/main/kotlin/com/bluebubbles/messaging/services/credentials/OBAutofillService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class OBAutofillService : AutofillService() {
167167
}
168168
client.destroy()
169169
}
170-
})
170+
}, null)
171171
}
172172
}
173173
callback.onSuccess()

0 commit comments

Comments
 (0)