Skip to content

Commit 2a68064

Browse files
committed
Update constellation base for asterism
2 parents d2d0b6e + b7d91f0 commit 2a68064

14 files changed

Lines changed: 422 additions & 5 deletions

File tree

play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,24 @@ object SettingsContract {
220220
)
221221
}
222222

223+
object Constellation {
224+
const val ID = "constellation"
225+
fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), ID)
226+
fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$ID"
227+
228+
const val PHONE_NUMBER_VERIFICATION_ENABLED = "phone_number_verification_enabled"
229+
const val PHONE_NUMBER_VERIFICATION_LAST_PACKAGE = "phone_number_verification_last_package"
230+
const val PHONE_NUMBER_VERIFICATION_LAST_USED_AT_MS = "phone_number_verification_last_used_at_ms"
231+
const val PHONE_NUMBER_VERIFICATION_LAST_SUCCESSFUL = "phone_number_verification_last_successful"
232+
233+
val PROJECTION = arrayOf(
234+
PHONE_NUMBER_VERIFICATION_ENABLED,
235+
PHONE_NUMBER_VERIFICATION_LAST_PACKAGE,
236+
PHONE_NUMBER_VERIFICATION_LAST_USED_AT_MS,
237+
PHONE_NUMBER_VERIFICATION_LAST_SUCCESSFUL,
238+
)
239+
}
240+
223241
object Profile {
224242
const val ID = "profile"
225243
fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), ID)

play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import android.preference.PreferenceManager
1818
import org.microg.gms.common.PackageUtils.warnIfNotMainProcess
1919
import org.microg.gms.settings.SettingsContract.Auth
2020
import org.microg.gms.settings.SettingsContract.CheckIn
21+
import org.microg.gms.settings.SettingsContract.Constellation
2122
import org.microg.gms.settings.SettingsContract.DroidGuard
2223
import org.microg.gms.settings.SettingsContract.Exposure
2324
import org.microg.gms.settings.SettingsContract.GameProfile
@@ -80,6 +81,7 @@ class SettingsProvider : ContentProvider() {
8081
Exposure.ID -> queryExposure(projection ?: Exposure.PROJECTION)
8182
SafetyNet.ID -> querySafetyNet(projection ?: SafetyNet.PROJECTION)
8283
DroidGuard.ID -> queryDroidGuard(projection ?: DroidGuard.PROJECTION)
84+
Constellation.ID -> queryConstellation(projection ?: Constellation.PROJECTION)
8385
Profile.ID -> queryProfile(projection ?: Profile.PROJECTION)
8486
Location.ID -> queryLocation(projection ?: Location.PROJECTION)
8587
Vending.ID -> queryVending(projection ?: Vending.PROJECTION)
@@ -103,6 +105,7 @@ class SettingsProvider : ContentProvider() {
103105
Exposure.ID -> updateExposure(values)
104106
SafetyNet.ID -> updateSafetyNet(values)
105107
DroidGuard.ID -> updateDroidGuard(values)
108+
Constellation.ID -> updateConstellation(values)
106109
Profile.ID -> updateProfile(values)
107110
Location.ID -> updateLocation(values)
108111
Vending.ID -> updateVending(values)
@@ -298,6 +301,31 @@ class SettingsProvider : ContentProvider() {
298301
editor.apply()
299302
}
300303

304+
private fun queryConstellation(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
305+
when (key) {
306+
Constellation.PHONE_NUMBER_VERIFICATION_ENABLED -> getSettingsBoolean(key, false)
307+
Constellation.PHONE_NUMBER_VERIFICATION_LAST_PACKAGE -> getSettingsString(key)
308+
Constellation.PHONE_NUMBER_VERIFICATION_LAST_USED_AT_MS -> getSettingsLong(key, 0L)
309+
Constellation.PHONE_NUMBER_VERIFICATION_LAST_SUCCESSFUL -> getSettingsBoolean(key, false)
310+
else -> throw IllegalArgumentException("Unknown key: $key")
311+
}
312+
}
313+
314+
private fun updateConstellation(values: ContentValues) {
315+
if (values.size() == 0) return
316+
val editor = preferences.edit()
317+
values.valueSet().forEach { (key, value) ->
318+
when (key) {
319+
Constellation.PHONE_NUMBER_VERIFICATION_ENABLED -> editor.putBoolean(key, value as Boolean)
320+
Constellation.PHONE_NUMBER_VERIFICATION_LAST_PACKAGE -> editor.putString(key, value as String?)
321+
Constellation.PHONE_NUMBER_VERIFICATION_LAST_USED_AT_MS -> editor.putLong(key, value as Long)
322+
Constellation.PHONE_NUMBER_VERIFICATION_LAST_SUCCESSFUL -> editor.putBoolean(key, value as Boolean)
323+
else -> throw IllegalArgumentException("Unknown key: $key")
324+
}
325+
}
326+
editor.apply()
327+
}
328+
301329
private fun queryProfile(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
302330
when (key) {
303331
Profile.PROFILE -> getSettingsString(key, "auto")

play-services-constellation/core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,6 @@ dependencies {
5959
implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
6060
api "com.squareup.wire:wire-runtime:$wireVersion"
6161
api "com.squareup.wire:wire-grpc-client:$wireVersion"
62+
63+
implementation "androidx.preference:preference-ktx:$preferenceVersion"
6264
}

play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import org.microg.gms.constellation.core.proto.ProceedResponse
1818
import org.microg.gms.constellation.core.proto.ServerTimestamp
1919
import org.microg.gms.constellation.core.proto.SyncResponse
2020
import org.microg.gms.constellation.core.proto.VerificationToken
21+
import org.microg.gms.settings.SettingsContract
22+
import org.microg.gms.settings.SettingsContract.Constellation
2123

2224
private const val STATE_PREFS_NAME = "constellation_prefs"
2325
private const val TOKEN_PREFS_NAME = "com.google.android.gms.constellation"
@@ -30,6 +32,11 @@ private const val KEY_PNVR_NOTICE_CONSENT = "pnvr_notice_consent"
3032
private const val KEY_PNVR_NOTICE_SOURCE = "pnvr_notice_source"
3133
private const val KEY_PNVR_NOTICE_VERSION = "pnvr_notice_version"
3234
private const val KEY_PNVR_NOTICE_UPDATED_AT_MS = "pnvr_notice_updated_at_ms"
35+
data class PhoneNumberVerificationRecord(
36+
val packageName: String,
37+
val usedAtMillis: Long,
38+
val successful: Boolean
39+
)
3340

3441
object ConstellationStateStore {
3542
fun loadVerificationTokens(context: Context): List<VerificationToken> {
@@ -130,6 +137,43 @@ object ConstellationStateStore {
130137
}
131138
}
132139

140+
fun isPhoneNumberVerificationEnabled(context: Context): Boolean =
141+
SettingsContract.getSettings(
142+
context,
143+
Constellation.getContentUri(context),
144+
arrayOf(Constellation.PHONE_NUMBER_VERIFICATION_ENABLED)
145+
) { it.getInt(0) != 0 }
146+
147+
fun setPhoneNumberVerificationEnabled(context: Context, enabled: Boolean) {
148+
SettingsContract.setSettings(context, Constellation.getContentUri(context)) {
149+
put(Constellation.PHONE_NUMBER_VERIFICATION_ENABLED, enabled)
150+
}
151+
}
152+
153+
fun recordPhoneNumberVerification(context: Context, packageName: String, successful: Boolean) {
154+
SettingsContract.setSettings(context, Constellation.getContentUri(context)) {
155+
put(Constellation.PHONE_NUMBER_VERIFICATION_LAST_PACKAGE, packageName)
156+
put(Constellation.PHONE_NUMBER_VERIFICATION_LAST_USED_AT_MS, System.currentTimeMillis())
157+
put(Constellation.PHONE_NUMBER_VERIFICATION_LAST_SUCCESSFUL, successful)
158+
}
159+
}
160+
161+
fun loadLastPhoneNumberVerification(context: Context): PhoneNumberVerificationRecord? {
162+
return SettingsContract.getSettings(
163+
context,
164+
Constellation.getContentUri(context),
165+
Constellation.PROJECTION
166+
) { cursor ->
167+
cursor.getString(1)?.let { packageName ->
168+
PhoneNumberVerificationRecord(
169+
packageName,
170+
cursor.getLong(2),
171+
cursor.getInt(3) != 0
172+
)
173+
}
174+
}
175+
}
176+
133177
private fun storeVerificationTokens(context: Context, tokens: List<VerificationToken>) {
134178
if (tokens.isEmpty()) return
135179
val filtered = tokens.filter {

play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,20 @@ import kotlinx.coroutines.withContext
2323
import org.microg.gms.common.Constants
2424
import org.microg.gms.constellation.core.proto.AsterismClient
2525
import org.microg.gms.constellation.core.proto.Consent
26+
import org.microg.gms.constellation.core.proto.ConsentVersion
2627
import org.microg.gms.constellation.core.proto.DeviceID
2728
import org.microg.gms.constellation.core.proto.GetConsentRequest
2829
import org.microg.gms.constellation.core.proto.GetConsentResponse
30+
import org.microg.gms.constellation.core.proto.Param
31+
import org.microg.gms.constellation.core.proto.RcsConsent
2932
import org.microg.gms.constellation.core.proto.RequestHeader
33+
import org.microg.gms.constellation.core.proto.SetConsentRequest
3034
import org.microg.gms.constellation.core.proto.SyncRequest
3135
import org.microg.gms.constellation.core.proto.Verification
3236
import org.microg.gms.constellation.core.proto.builder.RequestBuildContext
3337
import org.microg.gms.constellation.core.proto.builder.buildImsiToSubscriptionInfoMap
3438
import org.microg.gms.constellation.core.proto.builder.buildRequestContext
39+
import org.microg.gms.constellation.core.proto.builder.getList
3540
import org.microg.gms.constellation.core.proto.builder.invoke
3641
import org.microg.gms.constellation.core.verification.ChallengeProcessor
3742
import org.microg.gms.constellation.core.verification.MtSmsInboxRegistry
@@ -176,6 +181,10 @@ private suspend fun handleVerifyPhoneNumberRequest(
176181
var phoneNumbers = emptyList<PhoneNumberInfo>()
177182
var verifications = emptyArray<PhoneNumberVerification>()
178183
val status = try {
184+
if (!ConstellationStateStore.isPhoneNumberVerificationEnabled(context)) {
185+
throw PhoneNumberVerificationDisabledException()
186+
}
187+
179188
when (readCallbackMode) {
180189
ReadCallbackMode.LEGACY -> {
181190
Log.d(TAG, "Using read-only mode")
@@ -205,13 +214,25 @@ private suspend fun handleVerifyPhoneNumberRequest(
205214
} catch (e: Exception) {
206215
Log.e(TAG, "verifyPhoneNumber failed", e)
207216
when {
217+
e is PhoneNumberVerificationDisabledException -> Status(5000)
208218
readCallbackMode != ReadCallbackMode.NONE -> Status.INTERNAL_ERROR
209219
e is GrpcException -> handleRpcError(e)
210-
e is NoConsentException -> Status(5001)
211220
else -> Status.INTERNAL_ERROR
212221
}
213222
}
214223

224+
val successful = status.isSuccess && when (readCallbackMode) {
225+
ReadCallbackMode.NONE -> verifications.isNotEmpty() &&
226+
(request.targetedSims.isEmpty() ||
227+
verifications.size == request.targetedSims.size) &&
228+
verifications.all {
229+
it.verificationStatus == Verification.Status.STATUS_VERIFIED.toClientStatus()
230+
}
231+
232+
else -> true
233+
}
234+
ConstellationStateStore.recordPhoneNumberVerification(context, callingPackage, successful)
235+
215236
if (readCallbackMode == ReadCallbackMode.LEGACY ||
216237
readCallbackMode == ReadCallbackMode.NONE && legacyCallbackOnFullFlow
217238
) {
@@ -232,6 +253,8 @@ private suspend fun handleVerifyPhoneNumberRequest(
232253
)
233254
}
234255

256+
private class PhoneNumberVerificationDisabledException : Exception("Phone number verification is disabled")
257+
235258
private fun handleRpcError(error: GrpcException): Status {
236259
val statusCode = when (error.grpcStatus) {
237260
GrpcStatus.RESOURCE_EXHAUSTED -> 5008
@@ -245,8 +268,6 @@ private fun handleRpcError(error: GrpcException): Status {
245268
return Status(statusCode, error.message)
246269
}
247270

248-
private class NoConsentException : Exception("No consent")
249-
250271
private suspend fun runVerificationFlow(
251272
context: Context,
252273
request: VerifyPhoneNumberRequest,
@@ -278,8 +299,24 @@ private suspend fun runVerificationFlow(
278299
}
279300

280301
if (!consented) {
281-
Log.e(TAG, "Consent has not been set. Not running verification.")
282-
throw NoConsentException()
302+
Log.e(TAG, "Consent has not been set. Auto-setting consent.")
303+
val consentType = parseConsentVersion(request.extras)
304+
val setRequest = SetConsentRequest(
305+
header_ = RequestHeader(context, sessionId, buildContext, "setConsent"),
306+
asterism_client = asterismClient,
307+
rcs_consent = RcsConsent(
308+
consent = Consent.CONSENTED,
309+
consent_version = consentType
310+
),
311+
consent_version = consentType,
312+
api_params = Param.getList(request.extras)
313+
)
314+
try {
315+
RpcClient.phoneDeviceVerificationClient.SetConsent().execute(setRequest)
316+
Log.i(TAG, "Auto-consented for $asterismClient")
317+
} catch (e: Exception) {
318+
Log.w(TAG, "Auto-consent failed", e)
319+
}
283320
}
284321
}
285322

@@ -313,6 +350,14 @@ private suspend fun runVerificationFlow(
313350
return verifications
314351
}
315352

353+
private fun parseConsentVersion(extras: Bundle): ConsentVersion {
354+
val value = extras.getString("consent_type")
355+
val parsed = value?.toIntOrNull()?.let(ConsentVersion::fromValue)
356+
?: value?.let { runCatching { ConsentVersion.valueOf(it) }.getOrNull() }
357+
return parsed?.takeUnless { it == ConsentVersion.CONSENT_VERSION_UNSPECIFIED }
358+
?: ConsentVersion.RCS_DEFAULT_ON_LEGAL_FYI
359+
}
360+
316361
private fun PhoneNumberVerification.toLegacyPhoneNumberInfoOrNull(): PhoneNumberInfo? {
317362
if (
318363
verificationStatus != Verification.Status.STATUS_VERIFIED.toClientStatus() ||
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026, microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.microg.gms.constellation.core.ui
7+
8+
import android.content.Context
9+
import android.text.format.DateUtils
10+
import android.util.AttributeSet
11+
import android.util.DisplayMetrics
12+
import android.widget.ImageView
13+
import android.widget.TextView
14+
import androidx.appcompat.content.res.AppCompatResources
15+
import androidx.preference.Preference
16+
import androidx.preference.PreferenceViewHolder
17+
import org.microg.gms.constellation.core.R
18+
import org.microg.gms.ui.getApplicationInfoIfExists
19+
20+
class PhoneNumberVerificationAppPreference : Preference {
21+
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
22+
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
23+
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
24+
constructor(context: Context) : super(context)
25+
26+
var packageName: String? = null
27+
set(value) {
28+
field = value
29+
val packageManager = context.packageManager
30+
val applicationInfo = packageManager.getApplicationInfoIfExists(value)
31+
title = applicationInfo?.loadLabel(packageManager)?.toString() ?: value
32+
icon = applicationInfo?.loadIcon(packageManager)
33+
?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon)
34+
notifyChanged()
35+
}
36+
37+
var usedAtMillis: Long = 0L
38+
set(value) {
39+
field = value
40+
notifyChanged()
41+
}
42+
43+
init {
44+
isPersistent = false
45+
layoutResource = R.layout.preference_phone_number_verification_last_use
46+
}
47+
48+
override fun onBindViewHolder(holder: PreferenceViewHolder) {
49+
super.onBindViewHolder(holder)
50+
val icon = holder.findViewById(android.R.id.icon)
51+
if (icon is ImageView) {
52+
icon.adjustViewBounds = true
53+
icon.scaleType = ImageView.ScaleType.CENTER_INSIDE
54+
icon.maxHeight = (32.0 * context.resources.displayMetrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT).toInt()
55+
}
56+
holder.findViewById(R.id.phone_number_verification_last_use_time)?.let {
57+
(it as TextView).text = if (usedAtMillis > 0L) {
58+
DateUtils.getRelativeTimeSpanString(usedAtMillis)
59+
} else {
60+
null
61+
}
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026, microG Project Team
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.microg.gms.constellation.core.ui
7+
8+
import android.os.Bundle
9+
import androidx.preference.PreferenceCategory
10+
import androidx.preference.PreferenceFragmentCompat
11+
import org.microg.gms.constellation.core.ConstellationStateStore
12+
import org.microg.gms.constellation.core.R
13+
import org.microg.gms.ui.SwitchBarPreference
14+
15+
class PhoneNumberVerificationPreferencesFragment : PreferenceFragmentCompat() {
16+
private lateinit var enabled: SwitchBarPreference
17+
private lateinit var lastUseCategory: PreferenceCategory
18+
private lateinit var app: PhoneNumberVerificationAppPreference
19+
20+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
21+
addPreferencesFromResource(R.xml.preferences_phone_number_verification)
22+
}
23+
24+
override fun onBindPreferences() {
25+
enabled = preferenceScreen.findPreference(PREF_ENABLED) ?: enabled
26+
lastUseCategory = preferenceScreen.findPreference(PREF_LAST_USE_CATEGORY) ?: lastUseCategory
27+
app = preferenceScreen.findPreference(PREF_LAST_USE) ?: app
28+
enabled.setOnPreferenceChangeListener { _, newValue ->
29+
ConstellationStateStore.setPhoneNumberVerificationEnabled(
30+
requireContext(),
31+
newValue as Boolean
32+
)
33+
true
34+
}
35+
}
36+
37+
override fun onResume() {
38+
super.onResume()
39+
enabled.isChecked =
40+
ConstellationStateStore.isPhoneNumberVerificationEnabled(requireContext())
41+
val record = ConstellationStateStore.loadLastPhoneNumberVerification(requireContext())
42+
lastUseCategory.isVisible = record != null
43+
if (record != null) {
44+
app.packageName = record.packageName
45+
app.summary = getString(
46+
if (record.successful) {
47+
R.string.phone_number_verification_result_success
48+
} else {
49+
R.string.phone_number_verification_result_failure
50+
}
51+
)
52+
app.usedAtMillis = record.usedAtMillis
53+
}
54+
}
55+
56+
companion object {
57+
private const val PREF_ENABLED = "pref_phone_number_verification_enabled"
58+
private const val PREF_LAST_USE_CATEGORY = "prefcat_phone_number_verification_last_use"
59+
private const val PREF_LAST_USE = "pref_phone_number_verification_last_use"
60+
}
61+
}

0 commit comments

Comments
 (0)