Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5991207
Implement `play-services-constellation`
opstic Mar 24, 2026
330e932
Merge branch 'microg:master' into constellation
opstic Mar 24, 2026
977afd1
Refactor and small cleanups
opstic Mar 26, 2026
08b36eb
Refactor `org.microg.gms.constellation.core.verification`
opstic Mar 26, 2026
01400ff
More refactoring
opstic Mar 26, 2026
3f15e8b
Add `android.permission.USE_CREDENTIALS` into `AndroidManifest.xml`
opstic Mar 27, 2026
65961f8
Fix `NewApi` lints
opstic Mar 27, 2026
52a58d4
Fix the rest of the lints
opstic Mar 27, 2026
3917eeb
Convert parcelables into Java
opstic Mar 27, 2026
aec46b8
Small fixes
opstic Mar 27, 2026
23e80ba
Formatting
opstic Mar 27, 2026
59e1895
why did i write it like this
opstic Mar 27, 2026
b20cc3c
Remove full name and use import instead
opstic Mar 27, 2026
2552a9e
Use GMS package name instead of our own
opstic Mar 29, 2026
d08e4d8
Remove unnecessary dependencies
opstic Mar 29, 2026
05cd22d
Refactor package name from `builders` to `builder`
opstic Mar 29, 2026
117312d
Clean up `constellation.proto`
opstic Mar 29, 2026
db60565
More consistent formatting
opstic Mar 29, 2026
0621ee7
Use actual Gaia ID
opstic Apr 1, 2026
b82676c
Align more with official behavior
opstic Apr 3, 2026
58732b5
Add more infos to `TelephonyInfo`
opstic Apr 3, 2026
edb6de4
Improve DroidGuard generation
opstic Apr 3, 2026
94c442a
Improve `MtSmsVerifier`
opstic Apr 3, 2026
eddc24d
Reorder `RequestHeader` arguments
opstic Apr 3, 2026
85567ba
Improve RPC error handling
opstic Apr 4, 2026
f3449a7
Improve `MoSmsVerifier`
opstic Apr 4, 2026
6920424
Improve MT SMS timeout behavior
opstic Apr 4, 2026
fbcf53c
Add consent check before verification
opstic Apr 4, 2026
9904b50
Improve `CarrierIdVerifier`
opstic Apr 4, 2026
ec02375
Update `constellation.proto`
opstic Apr 5, 2026
ce85aeb
Small cleanups
opstic Apr 6, 2026
bafa5f4
Add permissions to Self-Check
opstic Apr 6, 2026
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
40 changes: 40 additions & 0 deletions play-services-constellation/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2026, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'signing'

android {
namespace "org.microg.gms.constellation"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

buildFeatures {
aidl = true
}

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

apply from: '../gradle/publish-android.gradle'

description = 'microG implementation of play-services-constellation'

dependencies {
implementation project(':play-services-basement')

annotationProcessor project(":safe-parcel-processor")
}
62 changes: 62 additions & 0 deletions play-services-constellation/core/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2026, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'kotlin-android'
apply plugin: 'signing'
apply plugin: 'com.squareup.wire'

android {
namespace "org.microg.gms.constellation.core"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = 1.8
}
}

wire {
kotlin {
rpcRole = 'client'
rpcCallStyle = 'suspending'
}
}

apply from: '../../gradle/publish-android.gradle'

description = 'microG service implementation for play-services-constellation'

dependencies {
api project(':play-services-constellation')

implementation project(':play-services-base-core')
implementation project(':play-services-iid')
implementation project(':play-services-auth-base')

implementation project(':play-services-droidguard')
implementation project(':play-services-tasks-ktx')

implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
api "com.squareup.wire:wire-runtime:$wireVersion"
api "com.squareup.wire:wire-grpc-client:$wireVersion"
}
33 changes: 33 additions & 0 deletions play-services-constellation/core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2026, microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission
android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.READ_SMS"
tools:ignore="SmsAndCallLogPolicy" />
<uses-permission
android:name="android.permission.SEND_SMS"
tools:ignore="SmsAndCallLogPolicy" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

<application>
<service android:name=".ConstellationApiService">
<intent-filter>
<action android:name="com.google.android.gms.constellation.service.START" />
</intent-filter>
</service>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.google.android.gms.constellation

import com.google.android.gms.constellation.GetPnvCapabilitiesResponse.VerificationCapability

enum class VerificationStatus(val value: Int) {
SUPPORTED(1),
UNSUPPORTED_CARRIER(2),
UNSUPPORTED_API_VERSION(3),
UNSUPPORTED_SIM_NOT_READY(4);

companion object {
fun fromInt(value: Int): VerificationStatus = when (value) {
2 -> UNSUPPORTED_CARRIER
3 -> UNSUPPORTED_API_VERSION
4 -> UNSUPPORTED_SIM_NOT_READY
else -> SUPPORTED
}
}
}

fun verificationCapability(
verificationMethod: Int,
status: VerificationStatus
): VerificationCapability {
return VerificationCapability(verificationMethod, status.value)
}

val VerificationCapability.status: VerificationStatus
get() = VerificationStatus.fromInt(statusValue)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.google.android.gms.constellation

import org.microg.gms.constellation.core.proto.VerificationMethod

val VerifyPhoneNumberRequest.verificationMethods: List<VerificationMethod>
get() = verificationMethodsValues.mapNotNull { VerificationMethod.fromValue(it) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.microg.gms.constellation.core

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.Base64
import androidx.annotation.RequiresApi
import androidx.core.content.edit
import com.google.android.gms.iid.InstanceID
import com.squareup.wire.Instant
import java.nio.charset.StandardCharsets
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec

val Context.authManager: AuthManager get() = AuthManager.get(this)

class AuthManager private constructor(context: Context) {
private val context = context.applicationContext
private val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

companion object {
private const val PREFS_NAME = "constellation_prefs"
private const val KEY_PRIVATE = "private_key"
private const val KEY_PUBLIC = "public_key"

// This is safe as the Context is immediately converted to the application context.
@SuppressLint("StaticFieldLeak")
@Volatile
private var instance: AuthManager? = null

fun get(context: Context): AuthManager = instance ?: synchronized(this) {
instance ?: AuthManager(context).also { instance = it }
}
}

// GMS signing format: {iidToken}:{seconds}:{nanos}
fun signIidTokenCompat(iidToken: String): Pair<ByteArray, Long> {
val currentTimeMillis = System.currentTimeMillis()

val epochSecond = currentTimeMillis / 1000
val nano = (currentTimeMillis % 1000) * 1_000_000

val content = "$iidToken:$epochSecond:$nano"
return sign(content) to currentTimeMillis
}

@RequiresApi(Build.VERSION_CODES.O)
fun signIidToken(iidToken: String): Pair<ByteArray, Instant> {
val (bytes, millis) = signIidTokenCompat(iidToken)
return bytes to Instant.ofEpochMilli(millis)
}

fun getIidToken(projectNumber: String? = null): String {
return try {
val sender = projectNumber ?: IidTokenPhenotypes.DEFAULT_PROJECT_NUMBER
InstanceID.getInstance(context).getToken(sender, "GCM")
} catch (_: Exception) {
""
}
}

fun getOrCreateKeyPair(): KeyPair {
val privateKeyStr = sharedPrefs.getString(KEY_PRIVATE, null)
val publicKeyStr = sharedPrefs.getString(KEY_PUBLIC, null)

if (privateKeyStr != null && publicKeyStr != null) {
try {
val kf = KeyFactory.getInstance("EC")
val privateKey = kf.generatePrivate(
PKCS8EncodedKeySpec(
Base64.decode(
privateKeyStr,
Base64.DEFAULT
)
)
)
val publicKey = kf.generatePublic(
X509EncodedKeySpec(
Base64.decode(
publicKeyStr,
Base64.DEFAULT
)
)
)
return KeyPair(publicKey, privateKey)
} catch (_: Exception) {
// Fall through to regeneration on failure
}
}

val kpg = KeyPairGenerator.getInstance("EC")
kpg.initialize(256)
val kp = kpg.generateKeyPair()

sharedPrefs.edit {
putString(KEY_PRIVATE, Base64.encodeToString(kp.private.encoded, Base64.NO_WRAP))
putString(KEY_PUBLIC, Base64.encodeToString(kp.public.encoded, Base64.NO_WRAP))
}

return kp
}

fun sign(content: String): ByteArray {
return try {
val kp = getOrCreateKeyPair()
val signature = Signature.getInstance("SHA256withECDSA")
signature.initSign(kp.private)
signature.update(content.toByteArray(StandardCharsets.UTF_8))
signature.sign()
} catch (_: Exception) {
ByteArray(0)
}
}

fun getFid(): String = InstanceID.getInstance(context).id
}
Loading