Skip to content

Games: Added game account management #2918

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 67 commits into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
f64ec52
Games: Added game account management
DaVinci9196 May 24, 2025
780dad4
Merge branch 'microg:master' into fix_multi_game_sign_in
DaVinci9196 Jun 3, 2025
da72a9e
remove redundant judgments
DaVinci9196 Jun 3, 2025
f88ba18
Games login add permissions web
DaVinci9196 Jun 3, 2025
a9f0975
Modify layout and error display
DaVinci9196 Jun 3, 2025
ac5f48c
change default
DaVinci9196 Jul 12, 2025
2204761
cleancode
DaVinci9196 Jul 12, 2025
5aa5be1
cleancode
DaVinci9196 Jul 12, 2025
256d475
cleancode
DaVinci9196 Jul 12, 2025
f49c4cc
cleancode
DaVinci9196 Jul 12, 2025
598f5dc
Modified to <2025 microG Project Team>
DaVinci9196 Jul 14, 2025
52b31df
remove GPlayGame Logo
DaVinci9196 Jul 16, 2025
e4f2f15
Measurement: Update to latest revision
mar-v-in Jun 6, 2025
585360b
Add some missing package info
mar-v-in Jun 9, 2025
79312a5
Ads Dynamite: Inject user-agent into app's shared preferences
mar-v-in Jun 9, 2025
57b3657
Auth: Handle invalid account (#2865)
DaVinci9196 Jul 2, 2025
ba939ea
Added translation using Weblate (Indonesian)
cyberboh Apr 25, 2025
aacb2bf
Translated using Weblate (Italian)
Fs00 Apr 25, 2025
830ba75
Translated using Weblate (Romanian)
ygorigor Apr 25, 2025
b678621
Translated using Weblate (Russian)
ItsRomeostar Apr 26, 2025
01680fe
Translated using Weblate (Chinese (Traditional Han script))
xlionjuan Apr 25, 2025
096b158
Translated using Weblate (Dutch)
ruditimmermans Apr 25, 2025
cccc76e
Translated using Weblate (Portuguese (Brazil))
lucasmz-dev Apr 26, 2025
26987fb
Translated using Weblate (Chinese (Simplified Han script))
hustler-not-chatty Apr 27, 2025
d058d60
Translated using Weblate (Irish)
aindriu80 Apr 30, 2025
72a2701
Added translation using Weblate (Persian)
alr86 May 4, 2025
d774a1a
Translated using Weblate (Russian)
May 8, 2025
5f2c838
Translated using Weblate (Polish)
userofinternet2023 May 17, 2025
3475746
Added translation using Weblate (Latvian)
Coool May 22, 2025
4026ee1
Translated using Weblate (Indonesian)
ca-kraa May 23, 2025
aac5e6f
Translated using Weblate (Uyghur)
AbduqadirAbliz May 27, 2025
90da81e
Translated using Weblate (Filipino)
searinminecraft May 28, 2025
792b389
Translated using Weblate (Arabic)
QSkill May 31, 2025
8a59b12
Translated using Weblate (Italian)
ale5000-git Jun 1, 2025
2de7b67
Translated using Weblate (Polish)
rehork Jun 1, 2025
cca51e1
Translated using Weblate (Chinese (Simplified Han script))
Jun 2, 2025
0b79396
Translated using Weblate (Italian)
Jun 11, 2025
250c777
Translated using Weblate (Thai)
grenadin Jun 10, 2025
6e2ee46
Translated using Weblate (Indonesian)
Jun 13, 2025
67fef83
Translated using Weblate (Tamil)
TamilNeram Jun 22, 2025
028966a
Translated using Weblate (Asturian)
enolp Jun 24, 2025
3506720
Translated using Weblate (Spanish)
AlejandroMoc Jun 26, 2025
9f52521
Phenotype: New configuration parameters (#2880)
DaVinci9196 Jul 11, 2025
448c6b9
Add dummy for App Engage Service (#2858)
DaVinci9196 Jul 11, 2025
f30438b
Vending: Expand payment methods (#2881)
DaVinci9196 Jul 11, 2025
9440c89
Change CompileSdk to 35 and Opt Out Edge to Edge (#2895)
p1gp1g Jul 11, 2025
b58c6e4
PoToken: Optimize po-token refresh logic (#2894)
DaVinci9196 Jul 11, 2025
8be3594
Return empty bundle on IAB getAlternativeBillingOnlyDialogIntent (#2914)
DaVinci9196 Jul 11, 2025
f203efa
Add com.android.vending.BILLING permission (#2930)
DaVinci9196 Jul 11, 2025
86c2158
HMS Maps: Add dark mode style (#2928)
DaVinci9196 Jul 11, 2025
d626566
Fix the names of the components in the self-check (#2927)
ale5000-git Jul 11, 2025
ae2ae3a
Add Wallet Service getClientToken dummy method (#2925)
DaVinci9196 Jul 11, 2025
50fa566
Vending: Add InAppReviewService dummy (#2955)
DaVinci9196 Jul 11, 2025
64d7e16
Google package: Add NotebookLM signature information (#2947)
DaVinci9196 Jul 11, 2025
cb1f6c7
Measurement: Make stub session id (#2946)
DaVinci9196 Jul 11, 2025
05b8f00
HMS Maps: Improve compatibility (#2945)
DaVinci9196 Jul 11, 2025
8bfc26a
Auth: Correctly include email scope in auth request (#2938)
DaVinci9196 Jul 11, 2025
927284e
AppMeasurement: Temporarily store UserProperties for apps (#2934)
DaVinci9196 Jul 11, 2025
062f831
Maps: Handle camera position update without valid lat/lng
mar-v-in Jul 13, 2025
ca6b7ca
Auth: Don't reset account visibility if already set
DaVinci9196 Jul 13, 2025
8f6b132
Phenotype: Add supported Gemini languages for GMail app
DaVinci9196 Jul 13, 2025
58b91c7
DynamicLink: Optimized the code (#2908)
DaVinci9196 Jul 13, 2025
5955157
DroidGuard: Add configuration switch to turn off hardware attestation…
mar-v-in Jul 15, 2025
87d08b6
Asset: Optimize resource download logic (#2957)
DaVinci9196 Jul 15, 2025
5dee65e
Merge branch 'master' into fix_multi_game_sign_in
DaVinci9196 Jul 17, 2025
375a52c
Revert "Modified to <2025 microG Project Team>"
DaVinci9196 Jul 17, 2025
3071431
Reapply "Modified to <2025 microG Project Team>"
DaVinci9196 Jul 17, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,20 @@ object SettingsContract {
)
}

object GameProfile {
const val ID = "gameprofile"
fun getContentUri(context: Context) = Uri.withAppendedPath(getCrossProfileSharedAuthorityUri(context), ID)
fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$ID"

const val ALLOW_CREATE_PLAYER = "game_allow_create_player"
const val ALLOW_UPLOAD_GAME_PLAYED = "allow_upload_game_played"

val PROJECTION = arrayOf(
ALLOW_CREATE_PLAYER,
ALLOW_UPLOAD_GAME_PLAYED
)
}

private fun <T> withoutCallingIdentity(f: () -> T): T {
val identity = Binder.clearCallingIdentity()
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.microg.gms.settings.SettingsContract.Auth
import org.microg.gms.settings.SettingsContract.CheckIn
import org.microg.gms.settings.SettingsContract.DroidGuard
import org.microg.gms.settings.SettingsContract.Exposure
import org.microg.gms.settings.SettingsContract.GameProfile
import org.microg.gms.settings.SettingsContract.Gcm
import org.microg.gms.settings.SettingsContract.Location
import org.microg.gms.settings.SettingsContract.Profile
Expand Down Expand Up @@ -84,6 +85,7 @@ class SettingsProvider : ContentProvider() {
Location.ID -> queryLocation(projection ?: Location.PROJECTION)
Vending.ID -> queryVending(projection ?: Vending.PROJECTION)
WorkProfile.ID -> queryWorkProfile(projection ?: WorkProfile.PROJECTION)
GameProfile.ID -> queryGameProfile(projection ?: GameProfile.PROJECTION)
else -> null
}

Expand All @@ -106,6 +108,7 @@ class SettingsProvider : ContentProvider() {
Location.ID -> updateLocation(values)
Vending.ID -> updateVending(values)
WorkProfile.ID -> updateWorkProfile(values)
GameProfile.ID -> updateGameProfile(values)
else -> return 0
}
return 1
Expand Down Expand Up @@ -401,6 +404,27 @@ class SettingsProvider : ContentProvider() {
editor.apply()
}

private fun queryGameProfile(p: Array<out String>): Cursor = MatrixCursor(p).addRow(p) { key ->
when (key) {
GameProfile.ALLOW_CREATE_PLAYER -> getSettingsBoolean(key, true)
GameProfile.ALLOW_UPLOAD_GAME_PLAYED -> getSettingsBoolean(key, true)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}

private fun updateGameProfile(values: ContentValues) {
if (values.size() == 0) return
val editor = preferences.edit()
values.valueSet().forEach { (key, value) ->
when (key) {
GameProfile.ALLOW_CREATE_PLAYER -> editor.putBoolean(key, value as Boolean)
GameProfile.ALLOW_UPLOAD_GAME_PLAYED -> editor.putBoolean(key, value as Boolean)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
editor.apply()
}

private fun MatrixCursor.addRow(
p: Array<out String>,
valueGetter: (String) -> Any?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<string name="foreground_service_notification_text"><xliff:g example="Exposure Notification">%1$s</xliff:g> 正在后台运行。</string>
<string name="foreground_service_notification_big_text">对 <xliff:g example="microG Services">%1$s</xliff:g> 忽略电池优化,或者修改通知设置以隐藏此通知。</string>
<string name="menu_advanced">高级</string>
<string name="menu_game_managed">管理游戏账号</string>
<string name="list_no_item_none">无</string>
<string name="list_item_see_all">全部显示</string>
<string name="open_app">打开</string>
Expand Down
1 change: 1 addition & 0 deletions play-services-base/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<string name="foreground_service_notification_big_text">Exclude <xliff:g example="microG Services">%1$s</xliff:g> from battery optimizations or change notification settings to hide this notification.</string>

<string name="menu_advanced">Advanced</string>
<string name="menu_game_managed">Game Accounts Managed</string>

<string name="list_no_item_none">None</string>
<string name="list_item_see_all">See all</string>
Expand Down
65 changes: 65 additions & 0 deletions play-services-core-proto/src/main/proto/games.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package google.play.games.whitelisted.v1whitelisted;

option java_outer_classname = "GamesPlayersProto";

option java_package = "org.microg.gms.games";
option java_multiple_files = true;

service PlayersFirstParty {
rpc DeleteApplicationDataFirstParty (DeleteApplicationDataRequest) returns (DeleteApplicationDataResponse);
rpc DeletePlayerFirstParty (DeletePlayerRequest) returns (DeletePlayerResponse);
}

service ApplicationsFirstParty {
rpc ListApplicationsWithUserDataFirstParty (ListApplicationsWithUserDataRequest) returns (ListApplicationsWithUserDataResponse);
}

message ListApplicationsWithUserDataRequest {
optional string locale = 1;
optional string androidSdk = 2;
}

message ListApplicationsWithUserDataResponse {
optional string tag = 1;
optional int32 code = 2;
repeated FirstPartyApplication firstPartyApplication = 3;
}

message FirstPartyApplication {
optional string tag = 1;
optional Application application = 2;
optional int32 unlockAchievementsNum = 6;
optional int32 played = 9;
}

message Application {
optional string tag = 1;
optional string gameId = 2;
optional string gameName = 3;
optional ApplicationIcon gameIcon = 7;
optional int32 achievementsNum = 10;
}

message ApplicationIcon {
optional string type = 1;
optional int32 width = 2;
optional int32 height = 3;
optional string url = 4;
optional string tag = 5;
}

message DeletePlayerRequest {}

message DeletePlayerResponse {}

message DeleteApplicationDataRequest {
optional string gameId = 1;
optional int32 status = 2;
}

message DeleteApplicationDataResponse {}
6 changes: 6 additions & 0 deletions play-services-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,12 @@

<!-- Games -->

<activity
android:name="org.microg.gms.games.ui.GamePlayDataActivity"
android:process=":ui"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>

<activity
android:name="org.microg.gms.games.ui.InGameUiActivity"
android:process=":ui"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ package org.microg.gms.auth.consent

import android.annotation.SuppressLint
import android.app.Activity
import android.content.res.Configuration
import android.graphics.Bitmap
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Message
import android.os.Messenger
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
Expand Down Expand Up @@ -58,11 +60,18 @@ class ConsentSignInActivity : Activity() {
finish()
return
}

initLayout()
initWebView()
initCookieManager()
}

private fun initLayout() {
val layoutParams = window.attributes as WindowManager.LayoutParams
layoutParams.width = (resources.displayMetrics.widthPixels * 0.8).toInt()
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
window.attributes = layoutParams
}

private fun initWebView() {
webView?.settings?.apply {
userAgentString = generateWebViewUserAgentString(userAgentString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ class AuthSignInActivity : AppCompatActivity() {
if (packageName == null || (packageName != callingActivity?.packageName && callingActivity?.packageName != this.packageName))
return finishResult(CommonStatusCodes.DEVELOPER_ERROR, "package name mismatch")

initView()
initView(packageName)
}

private fun initView() {
private fun initView(packageName: String) {
val accountManager = getSystemService<AccountManager>() ?: return finishResult(CommonStatusCodes.INTERNAL_ERROR, "No account manager")
val accounts = accountManager.getAccountsByType(DEFAULT_ACCOUNT_TYPE)
if (accounts.isNotEmpty()) {
Expand Down Expand Up @@ -234,6 +234,6 @@ class AuthSignInActivity : AppCompatActivity() {

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
initView()
config?.packageName?.let { initView(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.auth.api.signin.internal.ISignInCallbacks
import com.google.android.gms.auth.api.signin.internal.ISignInService
import com.google.android.gms.common.Feature
import com.google.android.gms.common.Scopes
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Scope
import com.google.android.gms.common.api.Status
Expand All @@ -42,6 +43,8 @@ import org.microg.gms.auth.AuthConstants
import org.microg.gms.auth.AuthPrefs
import org.microg.gms.common.GmsService
import org.microg.gms.common.PackageUtils
import org.microg.gms.games.GAMES_PACKAGE_NAME
import org.microg.gms.games.GamesConfigurationService
import org.microg.gms.utils.singleInstanceOf
import org.microg.gms.utils.warnOnTransactionIssues
import kotlin.coroutines.resume
Expand Down Expand Up @@ -79,10 +82,18 @@ class AuthSignInServiceImpl(
}
lifecycleScope.launchWhenStarted {
try {
val account = account
?: options?.account
?: SignInConfigurationService.getDefaultAccount(context, packageName)
?: AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).firstOrNull()
var currentAccount = account ?: options?.account
if (options?.scopes?.any { it.scopeUri.contains(Scopes.GAMES) } == true) {
currentAccount = currentAccount ?: GamesConfigurationService.getDefaultAccount(context, packageName)
if (currentAccount == null && GamesConfigurationService.loadPlayedGames(context)?.any { it == packageName } == false) {
currentAccount = GamesConfigurationService.getDefaultAccount(context, GAMES_PACKAGE_NAME)
}
if (currentAccount == null) {
sendResult(null, Status(CommonStatusCodes.SIGN_IN_REQUIRED))
return@launchWhenStarted
}
}
val account = currentAccount ?: SignInConfigurationService.getDefaultAccount(context, packageName)
Log.d(TAG, "silentSignIn: account -> ${account?.name}")
if (account != null && options?.isForceCodeForRefreshToken != true) {
if (getOAuthManager(context, packageName, options, account).isPermitted || AuthPrefs.isTrustGooglePermitted(context)) {
Expand Down Expand Up @@ -115,6 +126,9 @@ class AuthSignInServiceImpl(
Log.d(TAG, "$packageName:signOut defaultOptions:($defaultOptions)")
performSignOut(context, packageName, defaultOptions ?: options, account)
}
if (options?.scopes?.any { it.scopeUri.contains(Scopes.GAMES) } == true) {
GamesConfigurationService.setDefaultAccount(context, packageName, null)
}
SignInConfigurationService.setDefaultSignInInfo(context, packageName, null, null)
runCatching { callbacks.onSignOut(Status.SUCCESS) }
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.microg.gms.auth.consent.CONSENT_MESSENGER
import org.microg.gms.auth.consent.CONSENT_RESULT
import org.microg.gms.auth.consent.CONSENT_URL
import org.microg.gms.auth.consent.ConsentSignInActivity
import org.microg.gms.games.GamesConfigurationService
import org.microg.gms.people.DatabaseHelper
import org.microg.gms.utils.toHexString
import java.security.MessageDigest
Expand Down Expand Up @@ -58,6 +59,9 @@ val GoogleSignInOptions.includeProfile
val GoogleSignInOptions.includeUnacceptableScope
get() = scopeUris.any { it.scopeUri !in ACCEPTABLE_SCOPES }

val GoogleSignInOptions.includeGame
get() = scopeUris.any { it.scopeUri.contains(Scopes.GAMES) }

val consentRequestOptions: String?
get() = runCatching {
val sessionId = Base64.encodeToString(ByteArray(16).also { SecureRandom().nextBytes(it) }, Base64.NO_WRAP).trim()
Expand Down Expand Up @@ -153,6 +157,9 @@ suspend fun performSignIn(context: Context, packageName: String, options: Google
databaseHelper.close()
}
} else listOf(null, null, null, null)
if (options?.includeGame == true) {
GamesConfigurationService.setDefaultAccount(context, packageName, account)
}
SignInConfigurationService.setDefaultSignInInfo(context, packageName, account, options?.toJson())
return GoogleSignInAccount(
id,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2025 e foundation
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.games

import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import org.microg.gms.settings.SettingsContract

object GameProfileSettings {
private fun <T> getSettings(context: Context, vararg projection: String, f: (Cursor) -> T): T = SettingsContract.getSettings(
context, SettingsContract.GameProfile.getContentUri(context), projection, f
)

private fun setSettings(context: Context, v: ContentValues.() -> Unit) = SettingsContract.setSettings(context, SettingsContract.GameProfile.getContentUri(context), v)

@JvmStatic
fun setAllowCreatePlayer(context: Context, enabled: Boolean) {
setSettings(context) { put(SettingsContract.GameProfile.ALLOW_CREATE_PLAYER, enabled) }
}

@JvmStatic
fun setAllowUploadGamePlayed(context: Context, enabled: Boolean) {
setSettings(context) { put(SettingsContract.GameProfile.ALLOW_UPLOAD_GAME_PLAYED, enabled) }
}

@JvmStatic
fun getAllowCreatePlayer(context: Context): Boolean = getSettings(context, SettingsContract.GameProfile.ALLOW_CREATE_PLAYER) { c -> c.getInt(0) != 0 }

@JvmStatic
fun getAllowUploadGamePlayed(context: Context): Boolean = getSettings(context, SettingsContract.GameProfile.ALLOW_UPLOAD_GAME_PLAYED) { c -> c.getInt(0) != 0 }

}
Loading