diff --git a/build.gradle b/build.gradle index 92339b13f2..07307bbf5c 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ def execResult(... args) { } def ignoreGit = providers.environmentVariable('GRADLE_MICROG_VERSION_WITHOUT_GIT').getOrElse('0') == '1' -def gmsVersion = "24.09.13" +def gmsVersion = "24.47.35" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def vendingVersion = "40.2.26" def vendingVersionCode = Integer.parseInt(vendingVersion.replaceAll('\\.', '')) diff --git a/firebase-dynamic-links/build.gradle b/firebase-dynamic-links/build.gradle index 76f298c8e6..7e10a722e7 100644 --- a/firebase-dynamic-links/build.gradle +++ b/firebase-dynamic-links/build.gradle @@ -42,6 +42,7 @@ dependencies { // api project(':firebase-common-ktx') // api project(':firebase-components') api 'org.jetbrains.kotlin:kotlin-stdlib:1.7.10' + annotationProcessor project(':safe-parcel-processor') } apply from: '../gradle/publish-android.gradle' diff --git a/firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java b/firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java index 8ae5d431f6..a2b0e42dbf 100644 --- a/firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java +++ b/firebase-dynamic-links/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java @@ -10,12 +10,14 @@ import android.os.Parcel; import androidx.annotation.NonNull; import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; import android.os.Bundle; import android.net.Uri; import org.microg.gms.utils.ToStringHelper; +@SafeParcelable.Class public class DynamicLinkData extends AbstractSafeParcelable { @Field(1) public final String dynamicLink; @@ -35,6 +37,7 @@ public class DynamicLinkData extends AbstractSafeParcelable { @Field(6) public final Uri redirectUrl; + @Constructor public DynamicLinkData(@Param(1) String dynamicLink, @Param(2) String deepLink, @Param(3) int minVersion, @Param(4) long clickTimestamp, @Param(5) Bundle extensionBundle, @Param(6) Uri redirectUrl) { this.dynamicLink = dynamicLink; this.deepLink = deepLink; diff --git a/play-services-ads-identifier/core/src/main/res/values-ar/strings.xml b/play-services-ads-identifier/core/src/main/res/values-ar/strings.xml index f97014b069..7d308cd97a 100644 --- a/play-services-ads-identifier/core/src/main/res/values-ar/strings.xml +++ b/play-services-ads-identifier/core/src/main/res/values-ar/strings.xml @@ -1,7 +1,7 @@ - إشعار معرّف الإعلان - إذن معرّف الإعلان - يتيح للتطبيق تلقي إشعار عند تحديث معرّف الإعلان أو تحديد تفضيلات تتبع الإعلانات للمستخدم. - يتيح لتطبيق الناشر بالوصول إلى معرف إعلان صالح بشكل مباشر أو غير مباشر. + إشعار مُعَرِّف الإعلان + إذن مُعَرِّف الإعلان + يتيح للتطبيق تلقي إشعار عند تحديث مُعَرِّف الإعلان أو تحديد تفضيلات تتبع الإعلانات للمستخدم. + يتيح لتطبيق الناشر بالوصول إلى مُعَرِّف إعلان صالح بشكل مباشر أو غير مباشر. \ No newline at end of file diff --git a/play-services-ads-identifier/core/src/main/res/values-fr/strings.xml b/play-services-ads-identifier/core/src/main/res/values-fr/strings.xml index a6b3daec93..c82f144a93 100644 --- a/play-services-ads-identifier/core/src/main/res/values-fr/strings.xml +++ b/play-services-ads-identifier/core/src/main/res/values-fr/strings.xml @@ -1,2 +1,7 @@ - \ No newline at end of file + + Permission de l\'identifiant publicitaire + Autorise une application affichant de la publicité à accéder directement ou indirectement à un identifiant publicitaire valide. + Notification de l\'identifiant publicitaire + Autorise une application à être notifiée de la modification de l\'identifiant publicitaire ou de la limitation du suivi publicitaire de l\'utilisateur. + \ No newline at end of file diff --git a/play-services-ads-identifier/core/src/main/res/values-th/strings.xml b/play-services-ads-identifier/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..0204aa564c --- /dev/null +++ b/play-services-ads-identifier/core/src/main/res/values-th/strings.xml @@ -0,0 +1,7 @@ + + + อนุญาติรหัสประจำตัวของโฆษณา + การแจ้งเตือน รหัสประจำตัวของโฆษณา + อนุญาตให้แอปผู้เผยแพร่เข้าถึง รหัสประจำตัวของโฆษณาที่ถูกต้องได้โดยตรงหรือโดยอ้อม + อนุญาตให้แอปรับการแจ้งเตือนเมื่อมีการอัปเดต รหัสประจำตัวของโฆษณา หรือ การตั้งค่าการติดตามโฆษณาของผู้ใช้ + \ No newline at end of file diff --git a/play-services-ads-identifier/core/src/main/res/values-tr/strings.xml b/play-services-ads-identifier/core/src/main/res/values-tr/strings.xml index a6b3daec93..c505b7cddd 100644 --- a/play-services-ads-identifier/core/src/main/res/values-tr/strings.xml +++ b/play-services-ads-identifier/core/src/main/res/values-tr/strings.xml @@ -1,2 +1,7 @@ - \ No newline at end of file + + Reklam kimliği izni + Reklam kimliği bildirimi + Bir uygulamanın, kullanıcının reklam takibini kısıtlama ayarını veya reklam kimliğini değiştirdiğinde bildirim almasına izin verir. + Bir uygulamanın geçerli bir reklam kimliğine doğrudan veya dolaylı olarak erişmesine izin verir. + \ No newline at end of file diff --git a/play-services-ads-identifier/core/src/main/res/values-vi/strings.xml b/play-services-ads-identifier/core/src/main/res/values-vi/strings.xml index 4075236ae5..50c665e072 100644 --- a/play-services-ads-identifier/core/src/main/res/values-vi/strings.xml +++ b/play-services-ads-identifier/core/src/main/res/values-vi/strings.xml @@ -1,7 +1,7 @@ - Cho phép một ứng dụng nhận được thông báo khi ID quảng cáo hoặc giới hạn tùy chọn theo dõi quảng cáo của người dùng được cập nhật. - Quyền của Mã Quảng cáo - Thông báo của Mã Quảng cáo - Cho phép ứng dụng của nhà xuất bản truy cập trực tiếp hoặc gián tiếp vào một mã quảng cáo hợp lệ. + Cho phép ứng dụng nhận thông báo khi ID quảng cáo hoặc tùy chọn giới hạn theo dõi quảng cáo của người dùng được cập nhật. + Quyền ID quảng cáo + Thông báo ID quảng cáo + Cho phép ứng dụng của nhà xuất bản truy cập trực tiếp hoặc gián tiếp vào ID quảng cáo hợp lệ. \ No newline at end of file diff --git a/play-services-ads-lite/core/src/main/kotlin/com/google/android/gms/ads/measurement/DynamiteMeasurementManager.kt b/play-services-ads-lite/core/src/main/kotlin/com/google/android/gms/ads/measurement/DynamiteMeasurementManager.kt index 0ad53909ac..5959db3af6 100644 --- a/play-services-ads-lite/core/src/main/kotlin/com/google/android/gms/ads/measurement/DynamiteMeasurementManager.kt +++ b/play-services-ads-lite/core/src/main/kotlin/com/google/android/gms/ads/measurement/DynamiteMeasurementManager.kt @@ -4,7 +4,21 @@ */ package com.google.android.gms.ads.measurement +import android.os.Parcel +import android.util.Log import androidx.annotation.Keep +import com.google.android.gms.dynamic.IObjectWrapper +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "DynamiteMeasurement" @Keep -class DynamiteMeasurementManager : IMeasurementManager.Stub() +class DynamiteMeasurementManager : IMeasurementManager.Stub() { + + override fun initialize(context: IObjectWrapper?, proxy: IAppMeasurementProxy?) { + Log.d(TAG, "Not yet implemented: initialize") + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IAppMeasurementProxy.aidl b/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IAppMeasurementProxy.aidl new file mode 100644 index 0000000000..1fc75009af --- /dev/null +++ b/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IAppMeasurementProxy.aidl @@ -0,0 +1,5 @@ +package com.google.android.gms.ads.measurement; + +interface IAppMeasurementProxy { + +} \ No newline at end of file diff --git a/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IMeasurementManager.aidl b/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IMeasurementManager.aidl index e82159df4f..92f267b410 100644 --- a/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IMeasurementManager.aidl +++ b/play-services-ads-lite/src/main/aidl/com/google/android/gms/ads/measurement/IMeasurementManager.aidl @@ -1,5 +1,8 @@ package com.google.android.gms.ads.measurement; -interface IMeasurementManager { +import com.google.android.gms.ads.measurement.IAppMeasurementProxy; +import com.google.android.gms.dynamic.IObjectWrapper; +interface IMeasurementManager { + void initialize(IObjectWrapper context, IAppMeasurementProxy proxy) = 1; } \ No newline at end of file diff --git a/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt b/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt index 86b3f86f4e..074a891262 100644 --- a/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt +++ b/play-services-appinvite/core/src/main/kotlin/org/microg/gms/appinivite/AppInviteActivity.kt @@ -21,6 +21,8 @@ import androidx.core.view.setPadding import androidx.lifecycle.lifecycleScope import com.android.volley.* import com.android.volley.toolbox.Volley +import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer +import com.google.firebase.dynamiclinks.internal.DynamicLinkData import com.squareup.wire.Message import com.squareup.wire.ProtoAdapter import kotlinx.coroutines.CompletableDeferred @@ -28,7 +30,6 @@ import okio.ByteString.Companion.decodeHex import org.microg.gms.appinvite.* import org.microg.gms.common.Constants import org.microg.gms.utils.singleInstanceOf -import org.microg.gms.utils.toBase64 import java.util.* private const val TAG = "AppInviteActivity" @@ -37,6 +38,7 @@ private const val APPINVITE_DEEP_LINK = "com.google.android.gms.appinvite.DEEP_L private const val APPINVITE_INVITATION_ID = "com.google.android.gms.appinvite.INVITATION_ID" private const val APPINVITE_OPENED_FROM_PLAY_STORE = "com.google.android.gms.appinvite.OPENED_FROM_PLAY_STORE" private const val APPINVITE_REFERRAL_BUNDLE = "com.google.android.gms.appinvite.REFERRAL_BUNDLE" +private const val DYNAMIC_LINK_DATA = "com.google.firebase.dynamiclinks.DYNAMIC_LINK_DATA" class AppInviteActivity : AppCompatActivity() { private val queue by lazy { singleInstanceOf { Volley.newRequestQueue(applicationContext) } } @@ -71,6 +73,8 @@ class AppInviteActivity : AppCompatActivity() { } private fun open(appInviteLink: MutateAppInviteLinkResponse) { + val dynamicLinkData = DynamicLinkData(appInviteLink.metadata?.info?.url, appInviteLink.data_?.intentData, + (appInviteLink.data_?.app?.minAppVersion ?: 0).toInt(), System.currentTimeMillis(), null, null) val intent = Intent(Intent.ACTION_VIEW).apply { addCategory(Intent.CATEGORY_DEFAULT) data = appInviteLink.data_?.intentData?.let { Uri.parse(it) } @@ -83,17 +87,31 @@ class AppInviteActivity : AppCompatActivity() { APPINVITE_OPENED_FROM_PLAY_STORE to false ) ) + putExtra(DYNAMIC_LINK_DATA, SafeParcelableSerializer.serializeToBytes(dynamicLinkData)) } val fallbackIntent = Intent(Intent.ACTION_VIEW).apply { addCategory(Intent.CATEGORY_DEFAULT) data = appInviteLink.data_?.fallbackUrl?.let { Uri.parse(it) } } val installedVersionCode = runCatching { - intent.resolveActivity(packageManager)?.let { - PackageInfoCompat.getLongVersionCode(packageManager.getPackageInfo(it.packageName, 0)) + if (appInviteLink.data_?.packageName != null) { + PackageInfoCompat.getLongVersionCode(packageManager.getPackageInfo(appInviteLink.data_.packageName, 0)) + } else { + null } }.getOrNull() if (installedVersionCode != null && (appInviteLink.data_?.app?.minAppVersion == null || installedVersionCode >= appInviteLink.data_.app.minAppVersion)) { + val componentName = intent.resolveActivity(packageManager) + if (componentName == null) { + Log.w(TAG, "open resolve activity is null") + if (appInviteLink.data_?.packageName != null) { + val intentLaunch = + packageManager.getLaunchIntentForPackage(appInviteLink.data_.packageName) + if (intentLaunch != null) { + intent.setComponent(intentLaunch.component) + } + } + } startActivity(intent) finish() } else { diff --git a/play-services-auth-api-phone/core/src/main/res/values-fr/strings.xml b/play-services-auth-api-phone/core/src/main/res/values-fr/strings.xml index a6b3daec93..6bd087dd3c 100644 --- a/play-services-auth-api-phone/core/src/main/res/values-fr/strings.xml +++ b/play-services-auth-api-phone/core/src/main/res/values-fr/strings.xml @@ -1,2 +1,6 @@ - \ No newline at end of file + + Autoriser %s à lire le message ci-dessous et saisir le code ? + Autoriser + Refuser + \ No newline at end of file diff --git a/play-services-auth-api-phone/core/src/main/res/values-nl/strings.xml b/play-services-auth-api-phone/core/src/main/res/values-nl/strings.xml index a6b3daec93..cb45f89b6e 100644 --- a/play-services-auth-api-phone/core/src/main/res/values-nl/strings.xml +++ b/play-services-auth-api-phone/core/src/main/res/values-nl/strings.xml @@ -1,2 +1,6 @@ - \ No newline at end of file + + %s toestaan het onderstaande bericht te lezen en de code in te voeren? + Toestaan + Niet toestaan + \ No newline at end of file diff --git a/play-services-auth-api-phone/core/src/main/res/values-th/strings.xml b/play-services-auth-api-phone/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..9c741abb3f --- /dev/null +++ b/play-services-auth-api-phone/core/src/main/res/values-th/strings.xml @@ -0,0 +1,6 @@ + + + อนุญาตให้ %s อ่านข้อความด้านล่างและกรอกรหัส? + อนุญาต + ปฏิเสธ + \ No newline at end of file diff --git a/play-services-auth-api-phone/core/src/main/res/values-tr/strings.xml b/play-services-auth-api-phone/core/src/main/res/values-tr/strings.xml index a6b3daec93..30f73cd4b3 100644 --- a/play-services-auth-api-phone/core/src/main/res/values-tr/strings.xml +++ b/play-services-auth-api-phone/core/src/main/res/values-tr/strings.xml @@ -1,2 +1,6 @@ - \ No newline at end of file + + İzin ver + %s uygulamasının aşağıdaki mesajı okumasına ve kodu girmesine izin veriyor musunuz? + Reddet + \ No newline at end of file diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/common/KnownGooglePackages.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/common/KnownGooglePackages.kt index e0a644d762..ff727e157b 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/common/KnownGooglePackages.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/common/KnownGooglePackages.kt @@ -179,6 +179,18 @@ private val KNOWN_GOOGLE_PACKAGES = mapOf( PackageAndCertHash("com.google.android.apps.tasks", SHA256, "99f6cc5308e6f3318a3bf168bf106d5b5defe2b4b9c561e5ddd7924a7a2ba1e2"), setOf(ACCOUNT, AUTH, OWNER) ), + + // Google familylink + Pair( + PackageAndCertHash("com.google.android.apps.kids.familylink", SHA256, "6b58bb84c1c6d081d950448ff5c051a34769d7fd8d415452c86efeb808716c0e"), + setOf(ACCOUNT, AUTH, OWNER) + ), + + // Google Kids home + Pair( + PackageAndCertHash("com.google.android.apps.kids.home", SHA256, "8f7bd4c5c0273a1a0dd6b3bfa8cc8e9f980a25108adcfd7be9962e8ae9feeb6f"), + setOf(ACCOUNT, AUTH, OWNER) + ), ) fun isGooglePackage(pkg: PackageAndCertHash): Boolean { diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt index 5744f80ee9..78fde4870f 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt @@ -325,8 +325,10 @@ object ProfileManager { Log.v(TAG, "") } } - applyProfileData(profileData) - activeProfile = PROFILE_REMOTE + if (profileData.isNotEmpty()) { + applyProfileData(profileData) + activeProfile = PROFILE_REMOTE + } } fun getProfileName(context: Context, profile: String): String? = getProfileName { getProfileXml(context, profile) } @@ -377,6 +379,11 @@ object ProfileManager { } } + @JvmStatic + fun resetActiveProfile() { + activeProfile = null + } + @JvmStatic fun ensureInitialized(context: Context) { val metaData = runCatching { context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).metaData }.getOrNull() ?: Bundle.EMPTY diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 6c9355986d..4b5575bb3b 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -173,6 +173,7 @@ object SettingsContract { const val GEOCODER_NOMINATIM = "location_geocoder_nominatim" const val ICHNAEA_ENDPOINT = "location_ichnaea_endpoint" const val ONLINE_SOURCE = "location_online_source" + const val ICHNAEA_CONTRIBUTE = "location_ichnaea_contribute" val PROJECTION = arrayOf( WIFI_ICHNAEA, @@ -185,6 +186,7 @@ object SettingsContract { GEOCODER_NOMINATIM, ICHNAEA_ENDPOINT, ONLINE_SOURCE, + ICHNAEA_CONTRIBUTE, ) } @@ -196,11 +198,13 @@ object SettingsContract { const val LICENSING = "vending_licensing" const val LICENSING_PURCHASE_FREE_APPS = "vending_licensing_purchase_free_apps" const val BILLING = "vending_billing" + const val ASSET_DELIVERY = "vending_asset_delivery" val PROJECTION = arrayOf( LICENSING, LICENSING_PURCHASE_FREE_APPS, BILLING, + ASSET_DELIVERY, ) } diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 1b98a6d4a0..796f6feb85 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -10,12 +10,12 @@ import android.content.ContentValues import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences +import android.content.pm.ApplicationInfo import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.preference.PreferenceManager -import org.microg.gms.base.core.BuildConfig import org.microg.gms.common.PackageUtils.warnIfNotMainProcess import org.microg.gms.settings.SettingsContract.Auth import org.microg.gms.settings.SettingsContract.CheckIn @@ -23,12 +23,13 @@ import org.microg.gms.settings.SettingsContract.DroidGuard import org.microg.gms.settings.SettingsContract.Exposure import org.microg.gms.settings.SettingsContract.Gcm import org.microg.gms.settings.SettingsContract.Location -import org.microg.gms.settings.SettingsContract.Vending import org.microg.gms.settings.SettingsContract.Profile import org.microg.gms.settings.SettingsContract.SafetyNet +import org.microg.gms.settings.SettingsContract.Vending import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File + private const val SETTINGS_PREFIX = "org.microg.gms.settings." /** @@ -321,6 +322,7 @@ class SettingsProvider : ContentProvider() { Location.GEOCODER_NOMINATIM -> getSettingsBoolean(key, hasUnifiedNlpGeocoderBackend("org.microg.nlp.backend.nominatim") ) Location.ICHNAEA_ENDPOINT -> getSettingsString(key, null) Location.ONLINE_SOURCE -> getSettingsString(key, null) + Location.ICHNAEA_CONTRIBUTE -> getSettingsBoolean(key, false) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -341,6 +343,7 @@ class SettingsProvider : ContentProvider() { Location.GEOCODER_NOMINATIM -> editor.putBoolean(key, value as Boolean) Location.ICHNAEA_ENDPOINT -> (value as String).let { if (it.isBlank()) editor.remove(key) else editor.putString(key, it) } Location.ONLINE_SOURCE -> (value as? String?).let { if (it.isNullOrBlank()) editor.remove(key) else editor.putString(key, it) } + Location.ICHNAEA_CONTRIBUTE -> editor.putBoolean(key, value as Boolean) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -352,6 +355,7 @@ class SettingsProvider : ContentProvider() { Vending.LICENSING -> getSettingsBoolean(key, false) Vending.LICENSING_PURCHASE_FREE_APPS -> getSettingsBoolean(key, false) Vending.BILLING -> getSettingsBoolean(key, false) + Vending.ASSET_DELIVERY -> getSettingsBoolean(key, false) else -> throw IllegalArgumentException("Unknown key: $key") } } @@ -364,6 +368,7 @@ class SettingsProvider : ContentProvider() { Vending.LICENSING -> editor.putBoolean(key, value as Boolean) Vending.LICENSING_PURCHASE_FREE_APPS -> editor.putBoolean(key, value as Boolean) Vending.BILLING -> editor.putBoolean(key, value as Boolean) + Vending.ASSET_DELIVERY -> editor.putBoolean(key, value as Boolean) else -> throw IllegalArgumentException("Unknown key: $key") } } diff --git a/play-services-base/core/src/main/res/values-fr/strings.xml b/play-services-base/core/src/main/res/values-fr/strings.xml index baa3b65fe8..702a0b68bd 100644 --- a/play-services-base/core/src/main/res/values-fr/strings.xml +++ b/play-services-base/core/src/main/res/values-fr/strings.xml @@ -2,7 +2,7 @@ + --> Avancé Aucun Désactivé @@ -10,4 +10,10 @@ Automatique Manuel Actif en arrière-plan + Ouvrir + Exclure %1$s de l\'optimisation de la batterie ou modifier les paramètres des notifications pour désactiver cette notification. + Actif + Inactif + Tout voir + %1$s fonctionne en arrière-plan. \ No newline at end of file diff --git a/play-services-base/core/src/main/res/values-ja/strings.xml b/play-services-base/core/src/main/res/values-ja/strings.xml index d69b0d336f..aef68da780 100644 --- a/play-services-base/core/src/main/res/values-ja/strings.xml +++ b/play-services-base/core/src/main/res/values-ja/strings.xml @@ -12,7 +12,7 @@ 自動 手動 On - Off + オフ バックグラウンドで有効 %1$s をバックグラウンドで実行しています。 %1$s をバッテリー最適化から除外するか、通知設定でこの通知を非表示にしてください。 diff --git a/play-services-base/core/src/main/res/values-nl/strings.xml b/play-services-base/core/src/main/res/values-nl/strings.xml index a6b3daec93..910f1c35be 100644 --- a/play-services-base/core/src/main/res/values-nl/strings.xml +++ b/play-services-base/core/src/main/res/values-nl/strings.xml @@ -1,2 +1,16 @@ - \ No newline at end of file + + Actief op achtergrond + %1$s draait op de achtergrond. + Sluit <xliff:g example=“microG Services”>%1$s</xliff:g> uit van batterijoptimalisaties of wijzig de instellingen voor meldingen om deze melding te verbergen. + Geavanceerd + Geen + Zie alles + Open + Uitgeschakeld + Ingeschakeld + Automatisch + Manueel + Aan + Uit + \ No newline at end of file diff --git a/play-services-base/core/src/main/res/values-th/strings.xml b/play-services-base/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..30244e2e33 --- /dev/null +++ b/play-services-base/core/src/main/res/values-th/strings.xml @@ -0,0 +1,16 @@ + + + ทำงานในพื้นหลัง + %1$s กำลังทำงานอยู่ในพื้นหลัง + ไม่รวม %1$s จากการเพิ่มประสิทธิภาพแบตเตอรี่หรือเปลี่ยนการตั้งค่าการแจ้งเตือนเพื่อซ่อนการแจ้งเตือนนี้ + ขั้นสูง + ไม่มี + ดูทั้งหมด + เปิด + ปิดการทำงาน + เปิดใช้งานแล้ว + อัตโนมัติ + คู่มือ + เปิด + ปิด + \ No newline at end of file diff --git a/play-services-base/core/src/main/res/values-vi/strings.xml b/play-services-base/core/src/main/res/values-vi/strings.xml index 65b49ad8ee..99385cbb1d 100644 --- a/play-services-base/core/src/main/res/values-vi/strings.xml +++ b/play-services-base/core/src/main/res/values-vi/strings.xml @@ -1,13 +1,13 @@ - Loại trừ 1%1$s từ Tối ưu hoá pin hoặc thay đổi cài đặt của thông báo để ẩn thông báo này. + Bạn có thể loại trừ %1$s khỏi tính năng tối ưu hóa pin hoặc thay đổi cài đặt ứng dụng để ẩn thông báo này. Nâng cao Xem tất cả Tự động Không Mở - Tắt - Bật + Đã tắt + Đã bật Tắt Hoạt động trong nền Thủ công diff --git a/play-services-core/src/huawei/AndroidManifest.xml b/play-services-core/src/huawei/AndroidManifest.xml index cfe4c5bc51..a6ca2b9e94 100644 --- a/play-services-core/src/huawei/AndroidManifest.xml +++ b/play-services-core/src/huawei/AndroidManifest.xml @@ -31,8 +31,14 @@ + + \ No newline at end of file diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 6387adea54..c2a67c5d7e 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -61,6 +61,12 @@ android:label="@string/permission_service_writely_label" android:protectionLevel="dangerous" /> + + diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java index 1af77abffc..c0822cd618 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java @@ -190,11 +190,12 @@ protected void onBackButtonClicked() { } public void loginCanceled() { + Log.d(TAG, "loginCanceled: "); setResult(RESULT_CANCELED); if (response != null) { response.onError(AccountManager.ERROR_CODE_CANCELED, "Canceled"); } - finish(); + if (SDK_INT >= LOLLIPOP) { finishAndRemoveTask(); } else finish(); } @Override @@ -391,7 +392,7 @@ public void onResponse(AuthResponse response) { } checkin(true); returnSuccessResponse(account); - finish(); + if (SDK_INT >= LOLLIPOP) { finishAndRemoveTask(); } else finish(); } @Override @@ -677,7 +678,7 @@ public final void showView() { @JavascriptInterface public final void skipLogin() { Log.d(TAG, "JSBridge: skipLogin"); - finish(); + loginCanceled(); } @JavascriptInterface diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt index 5ba02536c7..7040c9dbed 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt @@ -80,6 +80,7 @@ private val SCREEN_ID_TO_URL = hashMapOf( 530 to "https://fit.google.com/privacy/settings", 547 to "https://myactivity.google.com/product/search", 562 to "https://myaccount.google.com/yourdata/youtube", + 580 to "https://families.google.com/kidonboarding", 10003 to "https://myaccount.google.com/personal-info", 10004 to "https://myaccount.google.com/data-and-privacy", 10005 to "https://myaccount.google.com/people-and-sharing", @@ -112,7 +113,8 @@ private val ALLOWED_WEB_PREFIXES = setOf( "https://policies.google.com/", "https://fit.google.com/privacy/settings", "https://maps.google.com/maps/timeline", - "https://myadcenter.google.com/controls" + "https://myadcenter.google.com/controls", + "https://families.google.com/kidonboarding" ) private val ACTION_TO_SCREEN_ID = hashMapOf( @@ -134,6 +136,7 @@ class MainActivity : AppCompatActivity() { val screenId = intent?.getIntExtra(EXTRA_SCREEN_ID, -1).takeIf { it != -1 } ?: ACTION_TO_SCREEN_ID[intent.action] ?: 1 val product = intent?.getStringExtra(EXTRA_SCREEN_MY_ACTIVITY_PRODUCT) + val kidOnboardingParams = intent?.getStringExtra(EXTRA_SCREEN_KID_ONBOARDING_PARAMS) val screenOptions = intent.extras?.keySet().orEmpty() .filter { it.startsWith(EXTRA_SCREEN_OPTIONS_PREFIX) } @@ -154,7 +157,13 @@ class MainActivity : AppCompatActivity() { } if (screenId in SCREEN_ID_TO_URL) { - val screenUrl = SCREEN_ID_TO_URL[screenId]?.run { if (screenId == 547 && !product.isNullOrEmpty()) { replace("search", product) } else { this } } + val screenUrl = SCREEN_ID_TO_URL[screenId]?.run { + if (screenId == 547 && !product.isNullOrEmpty()) { + replace("search", product) + } else if (screenId == 580 && !kidOnboardingParams.isNullOrEmpty()){ + "$this?params=$kidOnboardingParams" + } else { this } + } val layout = RelativeLayout(this) layout.addView(ProgressBar(this).apply { layoutParams = RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt index ddf9f1fb60..30a3b01be9 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt @@ -20,5 +20,6 @@ const val EXTRA_FALLBACK_URL = "extra.fallbackUrl" const val EXTRA_FALLBACK_AUTH = "extra.fallbackAuth" const val EXTRA_THEME_CHOICE = "extra.themeChoice" const val EXTRA_SCREEN_MY_ACTIVITY_PRODUCT = "extra.screen.myactivityProduct" +const val EXTRA_SCREEN_KID_ONBOARDING_PARAMS = "extra.screen.kidOnboardingParams" const val OPTION_SCREEN_FLAVOR = "screenFlavor" \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt index edf566356c..4ddfd56bd1 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/signin/AuthSignInActivity.kt @@ -8,6 +8,7 @@ package org.microg.gms.auth.signin import android.accounts.Account import android.accounts.AccountManager import android.content.Intent +import android.content.res.Configuration import android.graphics.Bitmap import android.os.Bundle import android.util.Log @@ -24,7 +25,6 @@ import com.google.android.gms.auth.api.identity.SignInCredential import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.auth.api.signin.SignInAccount import com.google.android.gms.auth.api.signin.internal.SignInConfiguration -import com.google.android.gms.common.Scopes import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Status import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer @@ -36,7 +36,6 @@ import org.microg.gms.auth.AuthConstants import org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT import org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE import org.microg.gms.auth.login.LoginActivity -import org.microg.gms.people.DatabaseHelper import org.microg.gms.people.PeopleManager import org.microg.gms.utils.getApplicationLabel @@ -53,8 +52,6 @@ class AuthSignInActivity : AppCompatActivity() { intent?.extras?.also { it.classLoader = SignInConfiguration::class.java.classLoader }?.getParcelable("config") }.getOrNull() - private val Int.px: Int get() = (this * resources.displayMetrics.density).toInt() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setResult(CommonStatusCodes.CANCELED) @@ -64,8 +61,12 @@ class AuthSignInActivity : AppCompatActivity() { val packageName = config?.packageName if (packageName == null || (packageName != callingActivity?.packageName && callingActivity?.packageName != this.packageName)) return finishResult(CommonStatusCodes.DEVELOPER_ERROR, "package name mismatch") - val accountManager = getSystemService() ?: return finishResult(CommonStatusCodes.INTERNAL_ERROR, "No account manager") + initView() + } + + private fun initView() { + val accountManager = getSystemService() ?: return finishResult(CommonStatusCodes.INTERNAL_ERROR, "No account manager") val accounts = accountManager.getAccountsByType(DEFAULT_ACCOUNT_TYPE) if (accounts.isNotEmpty()) { val account = config?.options?.account @@ -230,4 +231,9 @@ class AuthSignInActivity : AppCompatActivity() { } } } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + initView() + } } \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt index cac4c0f1f7..d20702e79f 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt @@ -18,6 +18,7 @@ class VendingFragment : PreferenceFragmentCompat() { private lateinit var licensingEnabled: TwoStatePreference private lateinit var licensingPurchaseFreeAppsEnabled: TwoStatePreference private lateinit var iapEnable: TwoStatePreference + private lateinit var assetDeliveryEnabled: TwoStatePreference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_vending) @@ -60,6 +61,18 @@ class VendingFragment : PreferenceFragmentCompat() { } true } + + assetDeliveryEnabled = preferenceScreen.findPreference(PREF_ASSET_DELIVERY_ENABLED) ?: assetDeliveryEnabled + assetDeliveryEnabled.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + if (newValue is Boolean) { + VendingPreferences.setAssetDeliveryEnabled(appContext, newValue) + } + updateContent() + } + true + } } override fun onResume() { @@ -73,6 +86,7 @@ class VendingFragment : PreferenceFragmentCompat() { licensingEnabled.isChecked = VendingPreferences.isLicensingEnabled(appContext) licensingPurchaseFreeAppsEnabled.isChecked = VendingPreferences.isLicensingPurchaseFreeAppsEnabled(appContext) iapEnable.isChecked = VendingPreferences.isBillingEnabled(appContext) + assetDeliveryEnabled.isChecked = VendingPreferences.isAssetDeliveryEnabled(appContext) } } @@ -80,5 +94,6 @@ class VendingFragment : PreferenceFragmentCompat() { const val PREF_LICENSING_ENABLED = "vending_licensing" const val PREF_LICENSING_PURCHASE_FREE_APPS_ENABLED = "vending_licensing_purchase_free_apps" const val PREF_IAP_ENABLED = "vending_iap" + const val PREF_ASSET_DELIVERY_ENABLED = "vending_asset_delivery" } } \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt index ec39c1586a..ec9e1a42dc 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023, e Foundation + * SPDX-FileCopyrightText: 2024 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -53,4 +54,19 @@ object VendingPreferences { put(SettingsContract.Vending.BILLING, enabled) } } + + @JvmStatic + fun isAssetDeliveryEnabled(context: Context): Boolean { + val projection = arrayOf(SettingsContract.Vending.ASSET_DELIVERY) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun setAssetDeliveryEnabled(context: Context, enabled: Boolean) { + SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) { + put(SettingsContract.Vending.ASSET_DELIVERY, enabled) + } + } } \ No newline at end of file diff --git a/play-services-core/src/main/res/drawable-v21/ic_app_settings_system.xml b/play-services-core/src/main/res/drawable-v21/ic_app_settings_system.xml index 3e10efc387..cfae14c650 100644 --- a/play-services-core/src/main/res/drawable-v21/ic_app_settings_system.xml +++ b/play-services-core/src/main/res/drawable-v21/ic_app_settings_system.xml @@ -7,7 +7,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="225.27" - android:viewportHeight="225.27"> + android:viewportHeight="225.27" + android:tint="?android:attr/colorControlNormal"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-ar/permissions.xml b/play-services-core/src/main/res/values-ar/permissions.xml index 3ebf8d2656..410a7947ce 100644 --- a/play-services-core/src/main/res/values-ar/permissions.xml +++ b/play-services-core/src/main/res/values-ar/permissions.xml @@ -41,15 +41,15 @@ جوجل Wi-Fi يتيح للتطبيق الوصول إلى جوجل Wi-Fi من خلال أي حساب جوجل مرتبط. اسم المستخدم على يوتيوب - عرض سجل أنشطة تطبيقات جوجل - عرض بيانات أدسنس - عرض وإدارة بيانات أدسنس - عرض بيانات تحليلات جوجل - عرض وإدارة بيانات تحليلات جوجل + عرض سِجِل أنشطة تطبيقات جوجل + عرض بياناتك في أدسنس + عرض وإدارة بياناتك في أدسنس + عرض بياناتك في تحليلات جوجل + عرض وإدارة بياناتك في تحليلات جوجل الوصول إلى مطور جوجل بلاي على أندرويد نطاق إدارة محرك التطبيقات. إذن قراءة وكتابة API ترحيل المجموعات. - عرض وإدارة إعدادات مجموعة تطبيقات جوجل + عرض وإدارة إعداداتك في مجموعة تطبيقات جوجل إذن القراءة والكتابة لـ API إدارة التراخيص. يتيح للتطبيق الوصول إلى جميع خدمات جوجل من خلال أي حساب جوجل مرتبط. خدمات أندرويد @@ -83,9 +83,9 @@ يتيح للتطبيق الوصول إلى يوتيوب من خلال أي حساب جوجل مرتبط. يتيح للتطبيق الوصول إلى اسم المستخدم على يوتيوب من خلال أي حساب جوجل مرتبط. إدارة إعدادات حساب المشتري في منصة التبادلات الإعلانية - عرض وإدارة بيانات أدسنس الخاصة بالاستضافة والحسابات المرتبطة - عرض بيانات منصة التبادلات الإعلانية - عرض وإدارة بيانات منصة التبادلات الإعلانية + عرض وإدارة بياناتك في أدسنس الخاصة بالاستضافة والحسابات المرتبطة + عرض بياناتك في منصة التبادلات الإعلانية + عرض وإدارة بياناتك في منصة التبادلات الإعلانية بالنسبة لمسؤولي ومستخدمي البائعين المعتمدين، إذن القراءة والكتابة عند الاختبار في وضع الملعب للـ API، أو إذن القراءة والكتابة عند استدعاء عملية API مباشرة. بالإضافة إلى نطاق بروتوكول أوث للقراءة والكتابة بشكل عام، استخدم نطاق بروتوكول أوث للقراءة فقط عند استرجاع بيانات العميل. الوصول إلى API لتدقيق الإدارة بصلاحية القراءة فقط @@ -109,4 +109,51 @@ يتيح الوصول إلى مجلد بيانات التطبيقات عرض تطبيقات جوجل درايف الخاصة بك عرض وإدارة ملفات جوجل درايف التي قمت بفتحها أو إنشائها باستخدام هذا التطبيق + نطاق خاص يستخدم للسماح للمستخدمين بالموافقة على تثبيت التطبيقات + عرض البيانات الوصفية للملفات والمستندات في جوجل درايف + عرض الملفات والمستندات في جوجل درايف + تعديل سلوك النصوص البرمجية النصية لتطبيقات جوجل + عرض وإدارة الملفات والمستندات في جوجل درايف + عرض حساب فريبيس + سَجِّل الدخول إلى فريبيس بحسابك + إدارة جداول الدمج من جوجل + عرض جداول الدمج من جوجل + نطاق للوصول إلى بيانات ألعاب جوجل بلاي. + إدارة بيانات GAN الخاص بك + عرض بيانات GAN الخاص بك + نطاق الجدول الزمني الزجاجي + إنشاء، قراءة، تحديث، وحذف المسودات. إرسال الرسائل والمسودات. + قراءة جميع الموارد وبياناتها الوصفية - لا توجد عمليات كتابة. + إدارة أفضل موقع متاح لك وسِجِل مواقعك + إدارة موقعك على مستوى المدينة وسِجِل مواقعك + إدارة أفضل موقع متاح لك + إدارة موقعك على مستوى المدينة + عرض وإدارة بيانات محرك خرائط جوجل + عرض بيانات محرك خرائط جوجل + عرض خرائط جوحل وإدارتها لتجربة الجوال + عرض بياناتك في أوركوت + إدارة نشاطك في أوركوت + اعرف من أنت على جوجل + المراسلة السحابية للكروم + جميع عمليات القراءة/الكتابة باستثناء عمليات الحذف الفوري والدائم للمواضيع والرسائل، متجاوزًا سلة المهملات. + تعرف على اسمك ومعلوماتك الأساسية وقائمة الأشخاص الذين تتواصل معهم على جوجل+ + إدارة بياناتك في واجهة برمجة تطبيقات التنبؤ من جوجل + عرض بيانات منتجاتك + إدارة قائمة المواقع والنطاقات التي تتحكم فيها + الوصول للقراءة/الكتابة إلى واجهة برمجة تطبيقات محتوى التسوق. + استهلاك المهام من قوائم مهامك + إدارة مهامك + إدارة مهامك + عرض مهامك + واجهة برمجة تطبيقات التتبع للخرائط جوجل، يسمح هذا النطاق بالوصول للقراءة والكتابة إلى بيانات مشروعك. + إدارة عناوين goo.gl الخاصة بك + عرض المعلومات الأساسية حول حسابك + إدارة حسابك في يوتيوب + عرض حسابك في يوتيوب + إدارة فيديوهاتك في يوتيوب + عرض التقارير النقدية لمحتواك على يوتيوب + عرض تحليلات يوتيوب لقناتك + عرض عنوان بريدك الإلكتروني + عرض وإدارة أصولك والمحتوى المرتبط على يوتيوب + إدارة عمليات التحقق من موقعك الجديد مع جوجل \ No newline at end of file diff --git a/play-services-core/src/main/res/values-ar/strings.xml b/play-services-core/src/main/res/values-ar/strings.xml index 18b92b63a6..cf243143f8 100644 --- a/play-services-core/src/main/res/values-ar/strings.xml +++ b/play-services-core/src/main/res/values-ar/strings.xml @@ -74,7 +74,7 @@ إعداد خدمات مايكرو-جي. اصبر قليلًا… جوجل - يحاول تطبيق على جهازك تسجيل الدخول إلى حساب جوجل. \u0020إن كان هذا مقصودًا، استخدم زر تسجيل الدخول للاتصال بصفحة تسجيل الدخول الخاصة بجوجل. إن لم يكن الأمر كذلك، اضغط على إلغاء للعودة إلى التطبيق الذي تسبب في ظهور هذه الرسالة. + يحاول تطبيق على جهازك تسجيل الدخول إلى حساب جوجل.\n\nإن كان هذا مقصودًا، استخدم زر تسجيل الدخول للاتصال بصفحة تسجيل الدخول الخاصة بجوجل. إن لم يكن الأمر كذلك، اضغط على إلغاء للعودة إلى التطبيق الذي تسبب في ظهور هذه الرسالة. يحتاج %1$s إذنك للوصول إلى حسابك على جوجل. يريد %1$s الوصول إلى حسابك وكأنه %2$s من %3$s. هذه قد يمنحه وصولًا مميزًا إلى حسابك. استمع إلى رسائل C2DM @@ -115,7 +115,7 @@ عند تفعيل هذا الخيار، لن تتضمن طلبات المصادقة اسم الجهاز، مما قد يسمح للأجهزة غير المصرح بها بتسجيل الدخول، لكن قد يؤدي ذلك إلى عواقب غير متوقعة. عند تفعيل هذا الخيار، ستتمكن جميع التطبيقات من رؤية عنوان البريد الإلكتروني لحسابات جوجل الخاصة بك دون الحصول على إذن مسبق. مُعَرِّف أندرويد - يسجل جهازك عند خدمات جوجل ويُنشئ معرّفًا فريدًا للجهاز. سيقوم مايكرو-جي بإزالة الأجزاء المُمَيِزة لك باستثناء اسم حساب جوجل الخاص بك في بيانات التسجيل. + يُسَجِلُ جهازك عند خدمات جوجل ويُنشئ معرّفًا فريدًا للجهاز. سيقوم مايكرو-جي بإزالة الأجزاء المُمَيِزة لك باستثناء اسم حساب جوجل الخاص بك في بيانات التسجيل. ليس مسجَّلًا آخر تسجيل: %1$s تسجيل الجهاز @@ -134,7 +134,7 @@ فترة التحقق: %1$s نبذه عن خدمات مايكرو-جي معلومات اﻹصدار والمكتبات الرمجية المستخدمة - خطأ في إلغاء التسجيل + حدث خطأ أثناء إلغاء التسجيل لم يعد مثبَّتًا إلغاء التسجيل لم تصل رسائل بعد @@ -164,4 +164,6 @@ التطبيقات المسجّلة التطبيقات غير المسجّلة الشبكات المستخدمة لإشعارات الدفع + خدمات مايكرو-جي المحدودة + تنبيهات حساب جوجل \ No newline at end of file diff --git a/play-services-core/src/main/res/values-ast/strings.xml b/play-services-core/src/main/res/values-ast/strings.xml index 0fd3e19543..b8f53480ef 100644 --- a/play-services-core/src/main/res/values-ast/strings.xml +++ b/play-services-core/src/main/res/values-ast/strings.xml @@ -9,9 +9,7 @@ Configuración de microG Xestor de cuentes de Google Servicios de microG - Hai una aplicación del preséu que tenta d\'aniciar la sesión nuna cuenta de Google -\n -\nSi esta solicitú foi intencionada, usa\'l botón Aniciar la sesión pa conectate a la páxina d\'aniciu de sesión de Google, si non primi Encaboxar pa volver a l\'aplicación que fexo qu\'esti diálogu apaeciere. + Hai una aplicación del preséu que tenta d\'aniciar la sesión nuna cuenta de Google \n \nSi esta solicitú foi intencionada, usa\'l botón Aniciar la sesión pa conectate a la páxina d\'aniciu de sesión de Google, si non primi Encaboxar pa volver a l\'aplicación que fexo qu\'esti diálogu apaeciere. Configura los servicios de microG. %1$s quier usar: \ No newline at end of file diff --git a/play-services-core/src/main/res/values-cs/strings.xml b/play-services-core/src/main/res/values-cs/strings.xml index 8cc5c64d86..a356349327 100644 --- a/play-services-core/src/main/res/values-cs/strings.xml +++ b/play-services-core/src/main/res/values-cs/strings.xml @@ -169,7 +169,7 @@ Služby microG Vteřinku… Pokračováním umožníte této aplikaci a společnosti Google používat vaše informace v souladu s jejich příslušnými smluvními podmínkami a zásadami ochrany osobních údajů. - Aplikace ve vašem zařízení se pokouší přihlásit do účtu Google. \u0020Pokud to byl záměr, použijte tlačítko Přihlásit se pro připojení se na přihlašovací stránku společnosti Google. Pokud ne, klepněte na Zrušit pro návrat zpět do aplikace, která vyvolala tento dialog. + Aplikace ve vašem zařízení se pokouší přihlásit do účtu Google.\n\nPokud to byl záměr, použijte tlačítko Přihlásit se pro připojení se na přihlašovací stránku společnosti Google. Pokud ne, klepněte na tlačítko Zrušit pro návrat zpět do aplikace, která vyvolala tento dialog. Nemáte přístup k síti. \n \nMůže se jednat o dočasný problém nebo vaše zařízení Android nemá přístup k mobilním datům. Zkuste to znovu, až budete připojeni k mobilní síti, nebo se připojte k síti Wi-Fi. @@ -242,4 +242,27 @@ Aplikace %1$s žádá o přístup k vašemu účtu, jako by byla aplikací %2$s od vývojáře %3$s. Tímto jí můžete udělit privilegovaný přístup k vašemu účtu. Automaticky přidávat bezplatné aplikace do knihovny Bezplatné aplikace mohou kontrolovat, zda byly staženy z Google Play. Automaticky přidávat bezplatné aplikace do knihovny vašeho účtu, abyste vždy prošli kontrolou u všech bezplatných aplikací, které máte aktuálně k dispozici. + Omezené služby microG + Používáte Omezené služby microG. Na rozdíl od klasických Služeb microG funguje tato varianta pouze s aplikacemi, které používají knihovny microG, nikoli ale s těmi, které používají Google Play. To znamená, že většina aplikací bude tyto služby ignorovat. + Rozumím + Upozornění účtu Google + Povolte cloudové zprávy + Po dokončení nastavení účtu můžete cloudové zprávy zakázat. + Povolte cloudové zprávy pro microG + Vaše zařízení se musí alespoň jednou zaregistrovat u společnosti Google.\n\nPo dokončení nastavení účtu můžete registraci zařízení Google zakázat. + Vyžadována akce účtu + Dokončete následující kroky, abyste mohli používat účet Google %s na tomto zařízení. + Povolte registrace zařízení + Váš účet Google potřebuje další nastavení. + Upozorní vás, pokud některý z účtů Google vyžaduje před použitím další nastavení nebo když účet není kompatibilní s microG. + Dokončete nastavení svého účtu Google + Dokončit + Krok dokončen + V závislosti na vašich předvolbách od vás potřebuje microG oprávnění, aby se mohl zaregistrovat do služby cloudových zpráv. + Váš účet Google spravuje vaše pracoviště nebo vzdělávací instituce. Váš správce rozhodl, že zařízení musí mít před přístupem k datům účtu bezpečný zámek obrazovky.\n\nNastavte zámek obrazovky s heslem, kódem PIN nebo vzorem. + Klepněte pro provedení kroku + Nastavte bezpečný zámek obrazovky + Povolit dodávání prostředků na vyžádání + Dodávání prostředků Google Play + Stáhnout další prostředky na žádost aplikací používajících službu Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-de/strings.xml b/play-services-core/src/main/res/values-de/strings.xml index f31c4209fd..64515ea682 100644 --- a/play-services-core/src/main/res/values-de/strings.xml +++ b/play-services-core/src/main/res/values-de/strings.xml @@ -16,7 +16,7 @@ --> microG-Dienste microG-Einstellungen - microG-Dienste einrichten + microG-Dienste einrichten. Einen kurzen Moment… Google Durch Fortsetzen erlaubst du dieser App und Google, deine Informationen nach ihren entsprechenden AGB und Datenschutzrichtlinien zu nutzen. @@ -24,9 +24,7 @@ %1$s versucht zu nutzen: Google Konto Manager Entschuldigung… - Eine App hat versucht, sich bei einem Google-Konto anzumelden. -\n -\nFalls dies beabsichtigt war, nutze die Schaltfläche Anmelden-, um Googles Anmeldeseite aufzurufen, andernfalls drücke Abbrechen, um zur App, die diesen Dialog verursacht hat, zurückzukehren. + Eine App auf ihren Gerät hat versucht, sich bei einem Google-Konto anzumelden. \n \nFalls dies beabsichtigt war, nutze die Schaltfläche Anmelden-, um Googles Anmeldeseite aufzurufen, andernfalls drücke Abbrechen, um zur App, die diesen Dialog verursacht hat, zurückzukehren. Einloggen "Dein Gerät verbindet sich mit den Google-Servern, um dich einzuloggen @@ -52,13 +50,13 @@ Versuche es später noch einmal." Stelle microG-Dienste bereit Erlaubt der app, microG-Dienste ohne Benutzerinteraktion zu konfigurieren Google-Geräte-Registrierung - Cloud Messaging + Cloud-Messaging Google SafetyNet - Google Play Games + Google Play Spiele %1$s möchte Play Games benutzen Um Play Games zu nutzen, ist die Installation der Google-Play-Games-App erforderlich. Diese App funktioniert eventuell auch ohne Play Games, verhält sich dabei unter Umständen aber ungewöhnlich. Ort auswählen - Ortsauswahl ist noch nicht verfügbar + Ortsauswahl ist noch nicht verfügbar. Diesen Ort auswählen Orte in der Nähe (%1$.7f, %2$.7f) @@ -142,7 +140,7 @@ Versuche es später noch einmal." Verbunden seit %1$s Push-Nachrichten erhalten Registrierung erlauben - Erlaubt es der App, sich für Push-Nachrichten zu registrieren + Erlaubt es der App, sich für Push-Nachrichten zu registrieren. App beim Erhalt einer Push-Nachricht starten App starten, während im Hintergrund die eingehenden Nachrichten abgerufen werden. Apps, die Push-Nachrichten benutzen @@ -256,6 +254,26 @@ Versuche es später noch einmal." Sie werden angemeldet weiter zu %1$s %1$s möchte auf Ihr Konto zugreifen, als ob es %2$s von %3$s wäre. Dies könnte einen privilegierten Zugang zu Ihrem Konto gewähren. - Erlauben Sie %1$s privilegierten Zugriff auf %2$s\? + Erlaube %1$s privilegierten Zugriff auf %2$s\? Rechnungsanfragen abwickeln + Limitierte microG Dienste + Dein Google-Konto wird von deinem Arbeitsplatz oder deiner Bildungseinrichtung verwaltet. Dein Administrator hat entschieden, dass Geräte eine sichere Bildschirmsperre benötigen, bevor sie auf Kontodaten zugreifen können.\n\nBitte richte eine Passwort-, PIN- oder Muster-Bildschirmsperre ein. + Google-Konto-Warnungen + Kontoaktion erforderlich + Benachrichtigt dich, wenn eines deiner Google-Konten zusätzliche Einrichtung erfordert, bevor es verwendet werden kann, oder wenn ein Konto nicht mit microG kompatibel ist. + Dein Google-Konto muss zusätzlich eingerichtet werden. + Beende das Einrichten deines Google-Kontos + Führe die folgenden Schritte aus, um dein Google-Konto %s auf diesem Gerät verwenden zu können. + Geräteregistrierung aktivieren + Cloud-Messaging aktivieren + Du kannst Cloud-Messaging nach Abschluss der Kontoeinrichtung deaktivieren. + Cloud-Messaging für microG zulassen + Dein Gerät muss sich mindestens einmal bei Google registrieren.\n\nDu kannst die Google-Geräteregistrierung deaktivieren, nachdem die Kontoeinrichtung abgeschlossen ist. + Gemäß deinen Einstellungen benötigt microG eine Erlaubnis von dir, bevor es sich für Cloud-Messaging registrieren kann. + Sichere Bildschirmsperre konfigurieren + Zum Ausführen des Schritts anklicken + Schritt abgeschlossen + Fertigstellen + Du verwendest die begrenzten microG-Dienste. Im Gegensatz zu den normalen microG-Diensten funktioniert diese Variante nur mit Apps, die microG-Bibliotheken verwenden, nicht mit denen von Google Play. Das bedeutet, dass die meisten Anwendungen diese Dienste ignorieren werden. + Ich habe es verstanden \ No newline at end of file diff --git a/play-services-core/src/main/res/values-es/permissions.xml b/play-services-core/src/main/res/values-es/permissions.xml index 3b04e83218..d908d640ab 100644 --- a/play-services-core/src/main/res/values-es/permissions.xml +++ b/play-services-core/src/main/res/values-es/permissions.xml @@ -72,7 +72,7 @@ Permite a la aplicación acceder a todos los servicios de Herramientas de Google Webmaster a través de cualquier cuenta de Google asociada. Búsqueda por voz Permite a la aplicación acceder a todos los servicios de Búsqueda por voz a través de cualquier cuenta de Google asociada. - Personalized Speech Recognition + Reconocimiento de Voz Personalizado Permite a la aplicación acceder a todos los servicios de Personalized Speech Recognition a través de cualquier cuenta de Google asociada. Google Talk Permite a la aplicación acceder a todos los servicios de Google Talk a través de cualquier cuenta de Google asociada. diff --git a/play-services-core/src/main/res/values-es/strings.xml b/play-services-core/src/main/res/values-es/strings.xml index c802703aa5..5e26d4838d 100644 --- a/play-services-core/src/main/res/values-es/strings.xml +++ b/play-services-core/src/main/res/values-es/strings.xml @@ -24,11 +24,9 @@ %1$s quiere usar: Administrador de cuentas de Google Lo sentimos… - Una aplicación de tu dispositivo está intentando iniciar sesión en una cuenta de Google. \u0020Si esto fue intencional, use el botón Iniciar sesión para conectarse a la página de inicio de sesión de Google, si no, presione Cancelar para volver a la aplicación que hizo que apareciera este cuadro de diálogo. + Una aplicación en tu dispositivo está intentando iniciar sesión en una cuenta de Google.\n\nSi esto fue intencionado, use el botón Iniciar sesión para conectarse a la página de inicio de sesión de Google; si no, presione Cancelar para volver a la aplicación que causó que este diálogo apareciera. Iniciar sesión - Tu dispositivo está estableciendo una conexión con los servidores de Google para iniciar sesión. -\n -\nEsto puede tardar unos segundos. + Tu dispositivo está estableciendo una conexión con los servidores de Google para iniciar sesión.\n\nEsto puede tardar unos segundos. "No tienes una conexión a Internet. Esto podría ser un problema temporal o tu dispositivo Android no esta configurado para los servicios de datos. Prueba otra vez cuando estés conectado a una red móvil o Wi-Fi." @@ -114,8 +112,7 @@ Esto podría tardar algunos minutos." Añadir cuenta de Google Cloud Messaging es un proveedor de notificaciones push usado por muchas aplicaciones. Para usarlo debes habilitar el registro del dispositivo. Intervalo del Cloud Messaging heartbeat - El intervalo en segundos para que el sistema realice un heartbeat a los servidores de Google. Aumentar este número reducirá el consumo de batería, pero puede causar retrasos en los mensajes push. -\nObsoleto, será reemplazado en futuras versiones. + El intervalo en segundos para que el sistema realice un heartbeat a los servidores de Google. Aumentar este número reducirá el consumo de batería, pero puede causar retrasos en los mensajes push.\nObsoleto, será reemplazado en futuras versiones. Aplicaciones usando Cloud Messaging Lista de aplicaciones registradas actualmente para Cloud Messagging. Confirmar nuevas aplicaciones @@ -132,10 +129,8 @@ Esto podría tardar algunos minutos." Registrado Registrado desde: %1$s ¿Cancelar el registro %1$s? - Algunas aplicaciones no se vuelven a registrar automáticamente y/o no ofrecen la opción de hacerlo manualmente. Es posible que estas aplicaciones no funcionen correctamente después de anular el registro. -\n¿Desea continuar? - Has denegado el registro de una aplicación para recibir notificaciones push que ya está registrada. -\n¿Quieres anular el registro ahora para que no reciba mensajes push en el futuro? + Algunas aplicaciones no se vuelven a registrar automáticamente y/o no ofrecen la opción de hacerlo manualmente. Es posible que estas aplicaciones no funcionen correctamente después de anular el registro.\n¿Desea continuar? + Has denegado el registro de una aplicación para recibir notificaciones push que ya está registrada.\n¿Quieres anular el registro ahora para que no reciba mensajes push en el futuro? Mensajes: %1$d (%2$d bytes) Desconectado Conectado desde %1$s @@ -256,4 +251,27 @@ Esto podría tardar algunos minutos." %1$s quiere acceder a tu cuenta como si fuera %2$s by %3$s. Esto podría otorgarle acceso privilegiado a su cuenta. Agregar automáticamente aplicaciones gratuitas a la biblioteca Las aplicaciones gratuitas pueden comprobar si se han descargado de Google Play. Añade aplicaciones gratuitas automáticamente a la biblioteca de tu cuenta para que siempre pasen la comprobación todas las aplicaciones gratuitas que estén disponibles actualmente. + Servicios limitados de microG + Entiendo + Está utilizando los Servicios Limitados de microG. A diferencia de los Servicios microG habituales, este sabor sólo funciona con aplicaciones que utilizan bibliotecas microG, no con las de Google Play. Esto significa que la mayoría de las aplicaciones ignorarán estos servicios. + Acción en la cuenta requerida + Su cuenta de Google necesita configuración adicional. + Habilitar Cloud Messaging + De acuerdo con sus preferencias, microG necesita permiso de usted antes de que pueda registrarse para Cloud Messaging. + Permitir Cloud Messaging para microG + Configurar bloqueo de pantalla seguro + Haga clic para realizar el paso + Paso completado + Alertas de la cuenta de Google + Notifica cuando una de sus cuentas de Google requiere una configuración adicional antes de poder usarse o cuando una cuenta es incompatible con microG. + Complete los siguientes pasos para poder usar su cuenta de Google %s en este dispositivo. + Finalizar la configuración de su cuenta de Google + Su dispositivo debe registrarse en Google al menos una vez.\n\nPuede desactivar el registro de dispositivos de Google después de que se complete la configuración de la cuenta. + Habilitar el registro del dispositivo + Puede desactivar Cloud Messaging después de que se complete la configuración de la cuenta. + Finalizar + Su cuenta de Google la administra su lugar de trabajo o institución educativa. Su administrador decidió que los dispositivos necesitan un bloqueo de pantalla seguro antes de poder acceder a los datos de la cuenta.\n\nConfigure una contraseña, PIN o patrón de bloqueo de pantalla. + Entrega de recursos de Google Play + Habilitar la entrega de activos a pedido + Descargar recursos adicionales cuando lo soliciten aplicaciones que utilicen Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-fil/strings.xml b/play-services-core/src/main/res/values-fil/strings.xml index 5a35b6abf7..a4484fe353 100644 --- a/play-services-core/src/main/res/values-fil/strings.xml +++ b/play-services-core/src/main/res/values-fil/strings.xml @@ -185,7 +185,7 @@ \n \nMaari ito isang pansamantalang problema o ang iyong Android device ay maaring hindi naka-provision para sa mga serbisyo ng data. Subukan muli kapag nakakonekta sa mobile network, o kumonekta sa Wi-Fi network. Magdagdag ng isa pang account - Ang isang app sa device mo ay sinusubukang mag-sign in sa isang Google Account. \u0020Kung sinasadya ito, gamitin ang Mag-sign in na button para kumonekta sa sign-in page ng Google, kung hindi, pindutin ang Kanselahin para bumalik sa application na naging sanhi ng pagpakita ng dialog na ito. + Ang isang app sa device mo ay sinusubukang mag-sign in sa isang Google Account.\n\nKung sinasadya ito, gamitin ang Mag-sign in na button para kumonekta sa sign-in page ng Google, kung hindi, pindutin ang Kanselahin para bumalik sa application na naging sanhi ng pagpakita ng dialog na ito. makinig sa mga internal status broadcast Pumili ng lugar Mga malapit na lugar @@ -242,4 +242,24 @@ Gustong i-access ng %1$s ang iyong account na kung ito ay %2$s ni/ng %3$s. Maaring bigyan ito ng pribilehiyong pag-access sa iyong account. Awtomatikong idagdag ang mga libreng app sa library Maaring suriin ng mga libreng app kapag na-download sila sa Google Play. Awtomatikong dagdagan ang mga libreng app sa iyong account library para palaging mapasa ang pagsuri para sa lahat ng mga libreng app na available sa iyo. + Mga Limitadong Serbisyo ng microG + Nangangailangan ng aksyon sa account + Nangangailangan ng iyong Google account ng karagdagang setup. + Buksan ang pagrehistro ng device + Buksan ang Cloud na Pagmemensahe + Maari mong i-disable ang Cloud na Pagmemensahe pagkatapos ng pag-set up ng account. + Payagan ang Cloud na Pagmemensahe para sa microG + Batay sa iyong mga kagustuhan, kinakailangan ng microG ang iyong pahintulot bago ito irehistro ang sarili para sa Cloud na Pagmemensahe. + Mag-configure ng secure na lock ng screen + Ang iyong Google account ay ipinamamahala ng iyong lugar ng trabaho o ang iyong institusyong pang-edukasyon. Nagpasya ang iyong tagapangasiwa na ang mga device ay kailangan ng secure na screen lock bago nila ma-access ang data ng account.\n\nPaki-set up ang password, PIN, o pattern na lock screen. + I-click para isagawa ang hakbang + Tapos na ang hakbang + Tapos na + Naiintindihan ko + Mga alerto sa Google account + Tapusing i-set up ang iyong Google account + Inaabisuhan kapag ang isa sa iyong mga Google account ay nangangailangan ng karagdagang setup bago ito magamit o kapag ang account ay hindi compatible sa microG. + Kumpletuhin ang mga sumusunod na hakbang upang magamit ang Google account na %s sa device na ito. + Kailangang i-rehistro ang iyong device sa Google kahit isang beses.\n\nMaari mong i-disable ang pagrehistro ng device sa Google pagkatapos ng pag-set up ng account. + Ginagamit mo ang Mga Limitadong Serbisyo ng microG. Hindi tulad ng nakasanayan na Mga Serbisyo ng microG, ang flavor na ito ay gumagana para sa mga app ba gumagamit ng mga library ng microG, hindi ang mula sa Google Play. Nangangahulugan nito na hindi papansinin ng mga karamihang application ang mga serbisyo na ito. \ No newline at end of file diff --git a/play-services-core/src/main/res/values-fr/permissions.xml b/play-services-core/src/main/res/values-fr/permissions.xml index fb6b4efb08..d28abf2251 100644 --- a/play-services-core/src/main/res/values-fr/permissions.xml +++ b/play-services-core/src/main/res/values-fr/permissions.xml @@ -13,30 +13,161 @@ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. - --> - - + --> Tous les services Google - Permets aux applications d\'accéder à tous les services Google par le biais de tous comptes associés à Google - + accéder à tous les services Google par le biais de tout compte Google associé. Services Android - Permets aux applications d\'accéder aux services Android par le biais de tous comptes associés à Google + accéder aux services Android par le biais de tout compte Google associé. Google AdSense - Pub - Permets aux applications d\'accéder à AdSense par le biais de tous comptes associés à Google - Google AdWords -Pub - Permets aux applications d\'accéder à AdWords par le biais de tous comptes associés à Google - Google App Engine - conception et hébergement d\'applications web basée sur les serveurs de Google. - Permets aux applications d\'accéder à Google App Engine le biais de tous comptes associés à Google + accéder à AdSense par le biais de tout compte Google associé. + Google AdWords - Pub + accéder à AdWords par le biais de tout compte Google associé. + Google App Engine - conception et hébergement d\'applications web basé sur les serveurs de Google + accéder à Google App Engine par le biais de tout compte Google associé. Google Blogger - Blogueur - Permets aux applications d\'accéder à Blogger par le biais de tous comptes associés à Google + accéder à Blogger par le biais de tout compte Google associé. Calendrier Google - Permets aux applications d\'accéder au Calendrier par le biais de tous comptes associés à Google - Google Contacts - Gérer vos contacts - Permets aux applications d\'accéder aux contacts par le biais de tous comptes associés à Google + accéder au Calendrier Google par le biais de tout compte Google associé. + Contacts Google + accéder à Contacts Google par le biais de tout compte Google associé. Dodgeball - Fournisseur de logiciels de réseaux sociaux basé sur l\'emplacement des fournisseurs de téléphonie mobile - Permets aux applications d\'accéder à Dodgeball par le biais de tous comptes associés à Google - Google Finance - Google Finance est un service en ligne publié par Google en 2006. Il permet de suivre le cours des actions et des devises. - Permets aux applications d\'accéder à Google Finance par le biais de tous comptes associés à Google. - Google Base - Google a mis officiellement en ligne ce service le 16 novembre 2005 et permet de mettre en ligne et de référencer tout type de contenu en termes d\'information. - Permets aux applications d\'accéder à Google Base par le biais de tous comptes associés à Google. - \ No newline at end of file + accéder à Dodgeball par le biais de tout compte Google associé. + Google Finance - Service en ligne publié par Google permettant de suivre le cours des actions et des devises + accéder à Google Finance par le biais de tout compte Google associé. + Google Base - service permettant de mettre en ligne et de référencer tout type de contenu en termes d\'information + accéder à Google Base par le biais de tout compte Google associé. + accéder à Google Play Développeur Android + Pour les utilisateurs et administrateurs revendeurs, accès lecture/écriture lors de tests dans la sandbox de l\'API, ou accès lecture/écriture lors d\'un appel direct pour une opération API. + Accès lecture/écriture à l\'API Groups Migration. + Gérer vos calendriers + Gérer vos données GAN + Voir vos calendriers + Voir et gérer vos données d\'impression Google Cloud + Voir et gérer vos ressources Google Compute Engine + Voir vos ressources Google Compute Engine + Voir vos données dans Google Cloud Storage + Voir et gérer vos jobs Google Maps Coordinate + Gérer vos données et permissions dans Google Cloud Storage + Gérer vos données dans Google Cloud Storage + Voir et gérer les rapports DoubleClick for Advertisers + Autoriser l\'accès au dossier Application Data + Voir vos applications Google Drive + Voir et gérer les fichiers que vous avez ouvert ou créé avec l\'application Google Drive + Cadre spécial utilisé pour permettre aux utilisateurs d\'approuver l\'installation d\'une application + Voir les métadonnées pour les fichiers et documents dans votre Google Drive + Voir les fichiers et documents dans votre Google Drive + Modifier le comportement de vos scripts Google Apps Script + Voir et gérer les fichiers et documents dans votre Google Drive + voir votre compte Freebase + Se connecter à votre compte Freebase + Gérer vos Tables Fusion + Voir vos Tables Fusion + Cadre pour accéder aux données de Google Play Games. + Voir vos données GAN + Étendue de la chronologie Glass + CloudMessaging pour Chrome + Créer, lire, modifier et supprimer des brouillons. Envoyer des messages et brouillons. + Toutes les opérations de lecture/écriture excepté les suppressions immédiates et permanentes de conversations et messages, sans passer par la corbeille. + Lire toutes les ressources et leurs métadonnées - pas d\'opération d\'écriture. + Gérer votre meilleure position disponible et historique de localisation + Gérer votre localisation approximative (nom de la ville) et historique de localisation + Gérer votre meilleur position disponible + Gérer votre localisation (ville) + Voir et gérer vos données Google Maps Engine + Voir vos données Google Maps Engine + Voir vos données Orkut + Voir et gérer votre expérience mobile Google Maps + Gérer votre activité Orkut + Identifier vos nom, informations basiques et liste de personnes auxquelles vous êtes connectées sur Google+ + Identifier qui vous êtes sur Google + Gérer vos données dans l\'API Google Prediction + Voir vos données produit + Gérer la liste des sites et domaines que vous contrôlez + Gérer les vérifications de votre nouveau site avec Google + Consommer les tâches de vos listes de tâches + Accès lecture/écriture à l\'API Shopping Content. + Gérer vos tâches + Voir votre adresse mail + API Google Maps Tracks, ce cadre permet un accès lecture/écriture aux données de votre projet. + Gérer vos URLs raccourcies goo.gl + Voir les informations de base sur votre compte + Gérer votre compte YouTube + Gérer vos vidéos YouTube + Voir et gérer vos actifs et contenus associés sur YouTube + Voir votre compte YouTube + Voir les rapports monétaires YouTube Analytics pour votre contenu YouTube + Voir les rapports YouTube Analytics pour votre contenu YouTube + Google Voice - Voix Google + accéder à JotSpot par le biais de tout compte Google associé. + accéder à iGoogle par le biais de tout compte Google associé. + JotSpot + Knol + accéder à Knol par le biais de tout compte Google associé. + Picasa Web Albums - Albums Web Picasa + accéder à Picasa Web Albums par le biais de tout compte Google associé. + Google Actualités + Google Maps + accéder à Google Maps par le biais de tout compte Google associé. + Mail Google + accéder à Google Mail par le biais de tout compte Google associé. + accéder à Google Actualités par le biais de tout compte Google associé. + Google Notebook + accéder à Google Notebook par le biais de tout compte Google associé. + Orkut + accéder à Orkut par le biais de tout compte Google associé. + Google Livres + accéder à Google Livres par le biais de tout compte Google associé. + Google Checkout accounts - Déconnexion comptes Google + Google Checkout QA accounts _ Déconnexion comptes Google QA + Google Checkout Sandbox accounts - Déconnexion comptes Google Sandbox + accéder à Google Checkout QA accounts par le biais de tout compte Google associé. + accéder à Google Checkout accounts par le biais de tout compte Google associé. + accéder à Google Checkout Sandbox accounts par le biais de tout compte Google associé. + accéder à Google Webmaster Tools par le biais de tout compte Google associé. + Google Webmaster Tools - Outils Google Webmaster + accéder à la recherche vocale par le biais de tout compte Google associé. + Recherche Vocale + accéder aux noms d\'utilisateurs YouTube par le biais de tout compte Google associé. + voir et gérer vos données Ad Exchange + Gérer vos tâches + Voir vos tâches + Google Groups - Groupes Google + accéder à Google Voice par le biais de tout compte Google associé. + accéder à Google Groups par le biais de tout compte Google associé. + Google Health - Santé Google + accéder à Google Health par le biais de tout compte Google associé. + iGoogle + Reconnaissance Vocale personnalisée + accéder à la reconnaissance vocale personnalisée par le biais de tout compte Google associé. + Google Talk + accéder à Google Talk par le biais de tout compte Google associé. + Google Wi-Fi + accéder à Google Wi-Fi par le biais de tout compte Google associé. + Google Spreadsheets + accéder à Google Spreadsheets par le biais de tout compte Google associé. + Google Docs + accéder à Google Docs par le biais de tout compte Google associé. + YouTube + accéder à YouTube par le biais de tout compte Google associé. + noms d\'utilisateurs YouTube + voir l\'historique d\'activité de vos applications Google + gérer votre configuration de compte acheteur Ad Exchange + Voir vos données Ad Exchange + voir vos données AdSense + voir et gérer vos données hôte AdSense et comptes associés + voir et gérer vos données AdSense + voir vos données Google Analytics + voir et gérer vos données Google Analytics + Périmètre App Engine Admin. + voir et gérer les paramètres d\'un Groupe Google Apps + En plus du cadre global lecture/écriture OAuth, utiliser le contexte lecture seule OAuth lors de la récupération des données du client. + Accès lecture/écriture à l\'API License Manager. + Accès à l\'API Admin Audit en lecture seule + Cadre pour l\'utilisation du service App State. + Voir vos données dans Google BigQuery + Voir et gérer vos données dans Google BigQuery + Gérer votre compte Blogger + Voir votre compte Blogger + Gérer vos livres + Voir vos jobs Google Coordinate + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-fr/plurals.xml b/play-services-core/src/main/res/values-fr/plurals.xml index 8731b34b6f..17e717b7c8 100644 --- a/play-services-core/src/main/res/values-fr/plurals.xml +++ b/play-services-core/src/main/res/values-fr/plurals.xml @@ -13,23 +13,25 @@ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. - --> - - + --> %1$d fournisseur configuré + %1$d fournisseurs configurés %1$d fournisseurs configurés %1$d application enregistrée + %1$d applications enregistrées %1$d applications enregistrées - Une autorisation requises pour le fonctionnement correct de microG Service est manquante. - Plusieurs autorisations requises pour le fonctionnement correct de microG Service sont manquantes. + Une autorisation requise pour le bon fonctionnement des services microG est manquante. + Plusieurs autorisations requises pour le bon fonctionnement des services microG sont manquantes. + Plusieurs autorisations requises pour le bon fonctionnement des services microG sont manquantes. - Demander la autorisation manquante + Demander l\'autorisation manquante + Demander les autorisations manquantes Demander les autorisations manquantes \ No newline at end of file diff --git a/play-services-core/src/main/res/values-fr/strings.xml b/play-services-core/src/main/res/values-fr/strings.xml index ead087b404..a5ba282dfd 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - microG Services + Services microG Paramètres de microG Juste une seconde… Google @@ -41,21 +41,21 @@ Ceci peut prendre plusieurs minutes." envoyer des messages C2DM aux autres applications échanger des messages et recevoir des notifications de synchronisation de la part des serveurs de Google Accès étendu aux services Google - Enregistrement du terminal auprès de Google + Enregistrement du terminal Cloud Messaging Google SafetyNet - Google Play Jeux - %1$s voudrait utiliser Play Jeux + Jeux Google Play + %1$s voudrait utiliser Jeux Google Play Pour utiliser Play Jeux il est requis d’installer l’application Google Play Jeux. L’application peut poursuivre sans Play Jeux, mais il est possible qu’elle se comporte de manière imprévue. Sélectionner un emplacement La sélection d’emplacement n’est pas encore disponible. Sélectionner cette position - Lieux environnants. + Lieux environnants (%1$.7f, %2$.7f) - microG Services: La permission %1$s est manquante. + Services microG : L\'autorisation %1$s est manquante Réseau mobile Wi-Fi - Roaming + Itinérance Autres réseaux @@ -68,15 +68,15 @@ Ceci peut prendre plusieurs minutes." C’est une indication forte que la ROM supporte l’usurpation de signature mais que celle-ci requiert une action supplémentaire pour être activée. Merci de consulter la documentation sur les éventuelles étapes nécessaires. Le système usurpe la signature : Merci de consulter la documentation sur les éventuelles étapes nécessaires. - Play Services (GmsCore) - Play Store (Phonesky) - Services Framework (GSF) + Services microG (GmsCore) + Compagnon microG (Phonesky) + microG Services Framework (GSF) %1$s installé : Installez l’application %1$s ou tout autre compatible. Merci de consulter la documentation pour obtenir la liste des applications compatibles. %1$s dispose de la bonne signature : - Soit l’application %1$s installée n’est pas compatible, soit l’usurpation de signature n’est pas activée pour celle-ci. Merci de consulter la documentation sur les applications et ROMs compatibles. + Soit l’application %1$s installée n’est pas compatible, soit l’usurpation de signature n’est pas active pour celle-ci. Merci de consulter la documentation sur les applications et ROMs compatibles. Optimisations de la batterie ignorées : - Appuyez ici pour désactiver les optimisations de la batterie. Des applications peuvent mal se comporter si vous ne le faites pas. + Appuyez ici pour désactiver les optimisations de la batterie. Des applications peuvent mal fonctionner si vous ne le faites pas. À propos @@ -87,7 +87,7 @@ Ceci peut prendre plusieurs minutes." Services Test Optimisations de la batterie activées - Vous avez activé Cloud Messaging mais l’optimisation de la batterie est activée pour microG Services. Afin de recevoir les notifications push vous devriez désactiver les optimisations de la batterie. + Vous avez activé Cloud Messaging mais l’optimisation de la batterie est activée pour les services microG. Afin de recevoir les notifications poussées vous devriez désactiver les optimisations de la batterie. Désactiver les optimisations de la batterie Autorisations manquantes Préférences du compte @@ -97,15 +97,16 @@ Ceci peut prendre plusieurs minutes." Si désactivé, l’utilisateur est interrogé avant que la demande d’autorisation d’une application soit envoyée à Google. Certaines applications échoueront à utiliser le compte Google si ceci est désactivé. Enregistre votre terminal auprès des services Google et crée un identifiant unique. microG retire les identifiants autres que le nom de votre compte Google des informations d’enregistrement. Plus - Cloud Messaging est un fournisseur de notifications push utilisé par beaucoup d’applications tierces. Pour l’utiliser vous devez activer l’enregistrement du terminal. + Cloud Messaging est un fournisseur de notifications poussées utilisé par beaucoup d’applications tierces. Pour l’utiliser vous devez activer l’enregistrement du terminal. Intervalle des signaux de présence Cloud Messaging - L’intervalle en secondes auquel le système signale sa présence aux serveurs de Google. Augmenter ce nombre réduira la consommation de batterie mais peu induire un délai dans la réception des messages push.\nDéprécié, sera remplacé dans une prochaine version. + L’intervalle en secondes auquel le système signale sa présence aux serveurs de Google. Augmenter ce nombre réduira la consommation de batterie mais peu induire un délai dans la réception des messages poussées. +\nDéprécié, sera remplacé dans une prochaine version. Applications utilisant Cloud Messaging Liste des applications actuellement enregistrées auprès de Cloud Messaging. Confirmation pour les nouvelles applications Demander avant d’enregistrer une nouvelle application auprès de Cloud Messaging Intervalle de ping : %1$s - À propos de microG Services + À propos des services microG Informations de version et librairies utilisées Erreur lors du désenregistrement Cette application n’est plus installée @@ -115,15 +116,173 @@ Ceci peut prendre plusieurs minutes." Dernier message : %1$s Enregistrée Enregistrée depuis : %1$s - Désenregistrer %1$s? - Certaines applications ne se réenregistrent pas et/ou ne fournisse pas de moyens de le faire manuellement. Ces applications peuvent ne plus fonctionner correctement après le désenregistrement.\nContinuer ? - Vous avez empêché une application déjà enregistrée de s’enregistrer pour recevoir des notifications push.\nVoulez-vous la désenregistrer maintenant pour qu’elle ne reçoive plus de notifications push à l’avenir ? + Désenregistrer %1$s ? + Certaines applications ne se réenregistrent pas et/ou ne fournissent pas de moyen de le faire manuellement. Ces applications peuvent ne plus fonctionner correctement après le désenregistrement.\nContinuer ? + Vous avez empêché une application déjà enregistrée de s’enregistrer pour recevoir des notifications poussées. +\nVoulez-vous la désenregistrer maintenant pour qu’elle ne reçoive plus de notifications poussées à l’avenir ? Messages : %1$d (%2$d octets) Déconnecté Connecté depuis %1$s - Google SafetyNet est un système de certification du terminal, assurant que celui-ci est correctement sécurisé et compatible avec Android CTS. Certaines applications utilisent SafetyNet pour des raisons de sécurité ou comme prérequis anti-altérations.\n\nmicroG GmsCore contient une implantation libre de SafetyNet, mais les serveurs officiels requièrent que les requêtes SafetyNet soient signées par le système propriétaire DroidGuard. + Google SafetyNet est un système de certification du terminal, assurant que celui-ci est correctement sécurisé et compatible avec Android CTS. Certaines applications utilisent SafetyNet pour des raisons de sécurité ou comme prérequis anti-altérations. +\n +\nLes services microG contiennent une implantation libre de SafetyNet, mais les serveurs officiels requièrent que les requêtes SafetyNet soient signées par le système propriétaire DroidGuard. Tester la certification SafetyNet Mode d’opération Paramétrer les services microG. Se connecter + Vitesse du véhicule + accéder à la vitesse de votre véhicule + accéder aux informations relatives au niveau de carburant de votre véhicule + Kilométrage du véhicule + Compte + Ajouter un compte Google + Tous les tests passés avec succès + Échec : %s + Profil appareil + Ajouter et gérer vos comptes Google + Alerte : %s + En cours… + Réel + INACTIF + Automatique : %s + ReCaptcha : %s + Copier les données JSON JWS + Recommandation + %1$s veut accéder à votre compte en tant que %2$s par %3$s. Ceci pourrait lui donner un accès privilégié à votre compte. + Choisir un compte + Ajouter un autre compte + Avant d\'utiliser cette appli, revoir ses %1$s et %2$s. + accéder aux informations de votre véhicule + accéder à la chaine de la marque du véhicule pour échanger des informations spécifiques à celui-ci + Retirer le nom du terminal lors de l\'authentification + Si activé, les requêtes d\'authentification n’incluront pas le nom du terminal, ce qui peut permettre à des appareils non autorisés à se connecter, mais peut aussi avoir des conséquences imprévisibles. + Comptes Google + Paramètres + Comptes + Attestation : %s + ReCaptcha Enterprise : %s + Type d\'évaluation + Applis utilisant SafetyNet + Effacer les requêtes récentes + Système : %s + Données de la réponse + Donnés de la requête + Recevoir des notifications poussées + Services limités microG + Autoriser à %1$s un accès privilégié à %2$s ? + pour continuer vers %1$s + Autoriser à vous connecter vers %1$s + Autoriser et partager + conditions générales + Pour continuer, microG va fournir à %1$s les éléments suivants de votre compte Google : nom, adresse mail et image de profil. + politique de confidentialité + lire la configuration des services Google + provision des services microG + Autorise l\'appli à configurer les services microG sans interaction de l’utilisateur + Informations du véhicule + Niveau de carburant du véhicule + accéder au informations relatives au kilométrage de votre véhicule + Chaine de la marque du véhicule + Démarrer l\'application en arrière-plan pour recevoir les messages poussés arrivants. + Applis utilisant les notifications poussées + Applis enregistrées + Test ReCAPTCHA Enterprise + L’exécution de DroidGuard n\'est pas supportée sur cet appareil. Les services SafetyNet pourraient mal fonctionner. + Natif + Importer un profil spécifique + Spécifique : %s + Si activé, toutes les applications de cet appareil auront la possibilité d\'accéder aux adresses mail de vos comptes Google sans autorisation préalable. + Authentification via l\'enregistrement du terminal + Autoriser les applis à accéder aux comptes + Non enregistré + Dernier enregistrement : %1$s + Enregistrer l\'appareil + Android ID + Statut de la réponse + Échec CTS + %s minutes + Une application de votre appareil tente de se connecter à un compte Google. +\n +\nSi c\'est intentionnel, utilisez le bouton Se connecter pour vous rendre sur la page de connexion Google. +\nAutrement, utilisez le bouton Annuler pour retourner dans l\'application qui a affiché cette fenêtre. + Dernière utilisation : %1$s + Services Play Store + Si désactivé, les requêtes d\'authentification ne seront pas liées à l\'enregistrement du terminal, ce qui peut permettre à des appareils non autorisés à se connecter, mais peut aussi avoir des conséquences imprévisibles. + Autoriser %1$s à s\'enregistrer pour recevoir des notifications poussées ? + Autoriser l\'enregistrement + Autoriser l\'appli à s’enregistrer pour recevoir des notifications poussées. + Démarrer l\'appli sur un message poussé + Applis désenregistrées + Réseaux à utiliser pour les messages poussés + Autoriser l\'attestation du terminal + Test ReCAPTCHA + Importer un profil spécifique depuis un fichier + Numéro d\'enregistrement + Choisir le profil + Utilisations récentes + Nonce (Hex) + Type de requête + Date de la requête + Données de base + Jeton + Copié dans le presse-papiers ! + Intégrité et CTS passés avec succès + Échec Intégrité + Pas encore complété + Pas de résultat + JSON invalide + ACTIF / Automatique : %s + ACTIF / Manuel : %s + %s secondes + Votre appareil est en train d\'établir une connexion avec les serveurs de Google pour vous authentifier. +\n +\nCela peut prendre quelques secondes. + Statut + Annuler + Continuer + Choisir un compte + pour continuer vers %1$s + Se connecter avec Google + Cette fonctionnalité est expérimentale et peut causer des pertes d\'argent. Vous êtes prévenus. + En cours de connexion + Récupération de licences activée + Ajouter automatiquement les applis gratuites à la bibliothèque + Certaines applis gratuites vérifient si elles ont été téléchargées depuis Google Play. Ajouter automatiquement à la bibliothèque de votre compte toutes les applis gratuites à votre disposition afin qu\'elles passent systématiquement ces vérifications. + Continuer en tant que %1$s + Se reconnecter à %1$s avec Google + Je comprends + Récupération de licences Google Play + Sauvegarde actuellement impossible + Facturation Google Play + Gérer les requêtes de facturation + Si activé, certaines applis pourront effectuer des achats ou initier des abonnements via le service de facturation Google Play. + Certaines applis peuvent aussi nécessiter d\'activer la récupération de licences pour vérifier vos achats. + Certaines applis exigent de vérifier que vous les avez achetées sur Google Play. Quand exigé par une appli, microG peut télécharger une preuve d\'achat en provenance de Google. Si désactivé ou si aucun compte Google sur l\'appareil, ces requêtes de vérification de licence seront ignorées. + Vous utilisez les services limités microG. Contrairement aux services microG standard, cette version fonctionne uniquement avec les librairies microG, et non celles de Google Play. Cela signifie que la plupart des applications ignoreront ces services. + Feedback actuellement impossible + Récupération de licences désactivée + En cours de connexion en tant que %1$s + En continuant, Google partagera avec %1$s votre nom, adresse mail et image de profil. Consultez la politique de confidentialité et les conditions générales de %1$s. + Gérer les requêtes de vérification de licence + Vous pouvez gérer Se Connecter avec Google dans votre compte Google. + Alertes de compte Google + Action requise sur le compte + Votre compte Google nécessite un paramètrage additionnel. + Terminez la configuration de votre compte Google + Complétez les étapes suivantes pour pouvoir utiliser votre compte Google %s sur cet appareil. + Activer l\'enregistrement du terminal + Activer Cloud Messaging + Vous pouvez désactiver Cloud Messaging une fois la configuration du compte terminée. + Autoriser Cloud Messaging pour microG + En accord avec votre choix, microG nécessite votre permission avant de pouvoir s\'enregistrer pour Cloud Messaging. + Configurer le verrouillage de sécurité de l\'écran + Appuyez pour effectuer l\'action + Indique lorsqu\'un de vos comptes Google requiert un paramètrage additionnel avant utilisation ou quand un compte est incompatible avec microG. + Votre appareil nécessite de s\'enregistrer à Google au moins une fois.\n\nVous pouvez désactiver l\'enregistrement de l\'appareil à Google une fois la configuration du compte terminée. + Votre compte Google est géré par votre entreprise ou votre établissement. Votre administrateur a établi que cet appareil nécessite un verrouillage d\'écran sécurisé avant de pouvoir accéder aux données du compte.\n\nVeuillez configurer un mot de passe, un code PIN ou un modèle de verrouillage écran. + Étape complétée + Terminer + Google Play Asset Delivery + Activer la distribution des packs d\'éléments à la demande + Télécharger les packs d\'éléments additionnels quand requis par les applis utilisant Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-ga/strings.xml b/play-services-core/src/main/res/values-ga/strings.xml index 6b0a06259e..5b77607de9 100644 --- a/play-services-core/src/main/res/values-ga/strings.xml +++ b/play-services-core/src/main/res/values-ga/strings.xml @@ -10,7 +10,7 @@ Tá do ghléas ag bunú nasc le freastalaithe Google chun tú a shíniú isteach. \n \nD\'fhéadfadh sé seo cúpla soicind a thógáil. - Tá aip ar do ghléas ag iarraidh síniú isteach ar Chuntas Google. Más rud é go raibh sé seo d\'aon ghnó, úsáid an cnaipe Sínigh isteach chun nascadh le leathanach síniú isteach Google, mura bhfuil, brúigh Cealaigh chun dul siar go dtí an feidhmchlár a léirigh an dialóg seo suas. + Tá aip ar do ghléas ag iarraidh síniú isteach ar Chuntas Google.\n\nMás rud é go raibh sé seo d\'aon ghnó, úsáid an cnaipe Sínigh isteach chun nascadh le leathanach síniú isteach Google, mura bhfuil, brúigh Cealaigh chun dul siar go dtí an feidhmchlár a léirigh an dialóg seo suas. Roghnaigh cuntas Cuir cuntas eile leis %1$s ag iarraidh rochtain a fháil ar do chuntas amhail is dá mba %2$s le %3$s. Seans go dtabharfaidh sé seo rochtain phribhléideach ar do chuntas dó. @@ -242,4 +242,27 @@ Lean ar aghaidh mar %1$s Cuir aipeanna saor in aisce leis an leabharlann go huathoibríoch Féadfaidh aipeanna saor in aisce a sheiceáil cibé an ndearnadh iad a íoslódáil ó Google Play. Cuir aipeanna saor in aisce le do leabharlann chuntais go huathoibríoch chun an tseic ar gach aip saor in aisce atá ar fáil duit faoi láthair a chur ar aghaidh i gcónaí. + Tá tú ag úsáid Seirbhísí Teoranta MicroG. Murab ionann agus na gnáthsheirbhísí microG, ní oibríonn an blas seo ach le haipeanna a úsáideann leabharlanna microG, ní iad siúd ar Google Play. Ciallaíonn sé seo go ndéanfaidh formhór na n-iarratas neamhaird ar na seirbhísí seo. + Seirbhísí Teoranta microG + Tuigim + Foláirimh chuntas Google + Gníomh cuntais ag teastáil + Críochnaigh do chuntas Google a shocrú + De réir do shainroghanna, teastaíonn cead uait ó microG sular féidir leis é féin a chlárú le haghaidh Cloud Messaging. + Cumraigh glas scáileáin slán + Cliceáil chun céim a dhéanamh + Céim críochnaithe + Tá socruithe breise de dhíth ar do Chuntas Google. + Comhlánaigh na céimeanna seo a leanas le bheith in ann do Chuntas Google %s a úsáid ar an ngléas seo. + Ní mór le do ghléas clárú le Google uair amháin ar a laghad.\n\nIs féidir leat clárú gléas Google a dhíchumasú tar éis socrú an chuntais a bheith críochnaithe. + Tugtar fógra nuair a bhíonn socruithe breise de dhíth ar cheann de do chuntais Google sular féidir é a úsáid nó nuair nach bhfuil cuntas ag luí le microG. + Cumasaigh clárú gléas + Tá do chuntas Google á bhainistiú ag d\'ionad oibre nó institiúid oideachais. Chinn do riarthóir go bhfuil glas scáileáin slán de dhíth ar ghléasanna sular féidir leo rochtain a fháil ar shonraí cuntais.\n\nSocraigh pasfhocal, UAP, nó scáileán glas patrún. + Cumasaigh Cloud Messaging + Is féidir leat Cloud Messaging a dhíchumasú nuair a bhíonn socrú an chuntais críochnaithe. + Ceadaigh Cloud Messaging le haghaidh microG + Críochnaigh + Íoslódáil sócmhainní breise nuair a iarrann apps a úsáideann Seachadadh Sócmhainní Súgartha + Cumasaigh seachadadh sócmhainní ar éileamh + Seachadadh Sócmhainní Google Play \ No newline at end of file diff --git a/play-services-core/src/main/res/values-it/permissions.xml b/play-services-core/src/main/res/values-it/permissions.xml index ab1d9ab950..34f884d249 100644 --- a/play-services-core/src/main/res/values-it/permissions.xml +++ b/play-services-core/src/main/res/values-it/permissions.xml @@ -100,8 +100,8 @@ Accesso in lettura e scrittura per l\'API Groups Migration. Visualizza e gestisci le impostazioni di un gruppo di applicazioni Google Accesso in lettura e scrittura per l\'API License Manager. - Leggi e scrivi gli accessi per amministratori ed utenti che eseguono test nel contenitore delle API, oppure leggi e scrivi gli accessi quando viene evocata una operazione API direttamente - In aggiunta all\'ambito complessivo di lettura e scrittura OAuth, utilizza l\'ambito di sola lettura OAuth quando richiedi i dati dell\'utente + Leggi e scrivi gli accessi per amministratori ed utenti che eseguono test nel contenitore delle API, oppure leggi e scrivi gli accessi quando viene evocata una operazione API direttamente. + In aggiunta all\'ambito complessivo di lettura e scrittura OAuth, utilizza l\'ambito di sola lettura OAuth quando richiedi i dati dell\'utente. Accedi in sola lettura per la API Admin Audit Ambito di utilizzo del servizio App State. Visualizza i tuoi dati di Google BigQuery @@ -137,8 +137,8 @@ Visualizza i tuoi dati GAN Messaggistica cloud per Chrome Ambito della sequenza temporale di Glass - Crea, leggi, aggiorna, cancella bozze ed invia messaggi - Tutte le operazioni di lettura e scrittura ad eccezione dell\'immediata e permanente cancellazione dei threads e dei messaggi, bypassando il cestino + Crea, leggi, aggiorna, cancella bozze ed invia messaggi. + Tutte le operazioni di lettura e scrittura ad eccezione dell\'immediata e permanente cancellazione dei threads e dei messaggi, bypassando il cestino. Visualizza tutte le risorse e i rispettivi metadati—nessuna operazione in scrittura. Gestisci la tua migliore posizione disponibile e la cronologia delle localizzazioni Gestisci la tua localizzazione a livello urbano e la cronologia delle localizzazioni @@ -160,7 +160,7 @@ Gestisci le tue attività Gestisci le tue attività Visualizza le tue attività - API per le tracce delle mappe Google: questo ambito permette la lettura e scrittura nella cartella dati del tuo progetto + API per le tracce delle mappe Google: questo ambito permette la lettura e scrittura nella cartella dati del tuo progetto. Gestisci i tuoi collegamenti rapidi goo.gl Visualizza il tuo indirizzo di posta elettronica Visualizza le informazioni di base sul tuo account diff --git a/play-services-core/src/main/res/values-it/strings.xml b/play-services-core/src/main/res/values-it/strings.xml index 50756054eb..43e1e787c5 100644 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -24,7 +24,7 @@ %1$s vorrebbe utilizzare: Gestione dell\'account Google Ci dispiace… - Un\'applicazione sul tuo dispositivo sta tentando di effettuare l\'accesso ad un account Google. \u0020Se ciò era intenzionale, utilizza il pulsante Accedi per collegarti alla pagina di autenticazione di Google, altrimenti premi Annulla per tornare all\'applicazione che ha aperto questa schermata. + Un\'applicazione sul tuo dispositivo sta tentando di effettuare l\'accesso ad un account Google.\n\nSe ciò era intenzionale, utilizza il pulsante Accedi per collegarti alla pagina di autenticazione di Google, altrimenti premi Annulla per tornare all\'applicazione che ha aperto questa schermata. Accedi "Il tuo dispositivo sta stabilendo la connessione con i server di Google per autenticarti. @@ -244,4 +244,27 @@ Questa operazione può richiedere alcuni secondi." Alcune applicazioni potrebbero richiedere anche di abilitare la verifica della licenza per verificare i tuoi acquisti. Questa funzionalità è ancora sperimentale e potrebbe comportare la perdita di denaro. Sei stato avvisato. Permetti a %1$s l\'accesso privilegiato a %2$s\? + microG Limited Services + Aggiungi automaticamente app gratuite alla libreria + Le app gratuite possono verificare se sono state scaricate da Google Play. Aggiungi automaticamente le app gratuite alla libreria del tuo account per superare sempre il controllo per tutte le app gratuite attualmente disponibili per te. + Ho capito + %1$s wants to access your account as if it was %2$s by %3$s. Ciò potrebbe garantirgli un accesso privilegiato al tuo account. + Stai utilizzando microG Limited Services. A differenza dei soliti servizi microG, questa versione funziona solo con le app che utilizzano le librerie microG, non quelle su Google Play. Ciò significa che la maggior parte delle applicazioni ignorerà questi servizi. + Avvisi dell\'account Google + Azione richiesta sull\'account + Il tuo account Google necessita di ulteriori configurazioni. + Completa la configurazione del tuo account Google + Completa i seguenti passaggi per poter utilizzare il tuo account Google %s su questo dispositivo. + Il tuo dispositivo deve registrarsi su Google almeno una volta.\n\nPuoi disattivare la registrazione del dispositivo Google dopo aver completato la configurazione dell\'account. + Abilita la registrazione del dispositivo + Abilita Cloud Messaging + Una volta completata la configurazione dell\'account, puoi disattivare Cloud Messaging. + Consenti Cloud Messaging per microG + Cliccare per confermare + Operazione completata + Fine + Ti avvisa quando uno dei tuoi account Google richiede una configurazione aggiuntiva prima di poter essere utilizzato o quando un account non è compatibile con microG. + In base alle tue preferenze, microG ha bisogno della tua autorizzazione prima di potersi registrare su Cloud Messaging. + Configurare il blocco schermo sicuro + Il tuo account Google è gestito dal tuo posto di lavoro o istituto scolastico. Il tuo amministratore ha deciso che i dispositivi necessitano di un blocco schermo sicuro prima di poter accedere ai dati dell\'account.\n\nImposta una password, un PIN o una sequenza per la schermata di blocco. \ No newline at end of file diff --git a/play-services-core/src/main/res/values-ja/permissions.xml b/play-services-core/src/main/res/values-ja/permissions.xml index a759cdb5bc..c2127069b2 100644 --- a/play-services-core/src/main/res/values-ja/permissions.xml +++ b/play-services-core/src/main/res/values-ja/permissions.xml @@ -100,4 +100,5 @@ Google Play Androd Developer へのアクセス Groups Migration API への読み取り/書き込みアクセス。 AdSense のデータを表示 + 管理監査APIへの読み取り専用のアクセス \ No newline at end of file diff --git a/play-services-core/src/main/res/values-nl/strings.xml b/play-services-core/src/main/res/values-nl/strings.xml index e1d8a1d61a..d58e5bf3ea 100644 --- a/play-services-core/src/main/res/values-nl/strings.xml +++ b/play-services-core/src/main/res/values-nl/strings.xml @@ -1,7 +1,7 @@ Google-accountbeheerder - Sign in + Aanmelden System has signature spoofing support: System grants signature spoofing permission: Add Google account @@ -11,29 +11,19 @@ microG-diensten microG-instellingen MicroG-services instellen. - Even… + Wacht even… Als u doorgaat, geeft u deze app en Google toestemming uw gegevens te gebruiken in overeenstemming met hun respectieve servicevoorwaarden en privacybeleid. Google Sorry… - An app on your device is trying to sign in to a Google account. -\n -\nIf this was intentional, use the Sign in button to connect to Google’s sign-in page, if not, press Cancel to go back to the application that caused this dialog to show up. - Your device is establishing a connection to Google’s servers to sign you in. -\n -\nThis can take a few seconds. - You don’t have a network connection. -\n -\nThis could be a temporary problem or your Android device may not be provisioned for data services. Try again when connected to a mobile network, or connect to a Wi-Fi network. - There was a problem communicating with Google servers. -\n -\nTry again later. - Your device is contacting Google to save information to your account. -\n -\nThis can take a couple of minutes. - Allow - Deny - Authentication required - <xliff:g example=F-Droid xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> requires your authorization to access your Google account. + Een app op uw apparaat probeert zich aan te melden bij een Google-account.\n\nAls dit de bedoeling was, gebruik dan de knop Aanmelden om verbinding te maken met de aanmeldingspagina van Google. Zo niet, druk dan op Annuleren om terug te gaan naar de toepassing die dit dialoogvenster heeft veroorzaakt. + Je apparaat maakt verbinding met de servers van Google om je aan te melden.\n\nDit kan enkele seconden duren. + Je hebt geen netwerkverbinding. \n \nDit kan een tijdelijk probleem zijn of je Android-toestel is mogelijk niet ingesteld op dataservices. Probeer het opnieuw als je verbinding hebt met een mobiel netwerk of maak verbinding met een Wi-Fi-netwerk. + Er is een probleem opgetreden bij de communicatie met de Google-servers. \n \nProbeer het later opnieuw. + Uw apparaat neemt contact op met Google om informatie op te slaan in uw account. \n \nDit kan een paar minuten duren. + Toestaan + Niet toestaan + Verificatie vereist + <xliff:g example=F-Droid xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> heeft uw autorisatie nodig om toegang te krijgen tot uw Google-account. Choose an account to continue to <xliff:g example=F-Droid xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> Add another account @@ -61,16 +51,16 @@ Car vendor channel Access your car\'s mileage information Access your car\'s vendor channel to exchange car-specific information - Google device registration - Cloud Messaging - Google SafetyNet - Play Store services + Google apparaat registratie + Cloud berichtenuitwisseling + Google Beveiligingsnet + Play Store diensten Google Play Games <xliff:g example=F-Droid xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> would like to use Play Games To use Play Games it is required to install the Google Play Games app. The application might continue without Play Games, but it is possible that it will behave unexpectedly. Pick a place Place picker is not yet available. - Select this location + Selecteer deze locatie Nearby places (%1$.7f, %2$.7f) microG Services: Lacking permission to <xliff:g example=have full network acccess xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> @@ -94,11 +84,11 @@ Either the installed <xliff:g example=F-Droid xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> is not compatible or signature spoofing is not active for it. Please check the documentation on which applications and ROMs are compatible. Battery optimizations ignored: Touch here to disable battery optimizations. Not doing this may result in misbehaving applications. - About + Over Components Configuration Google Services - Location service + Locatiedienst Services Test Battery optimizations enabled @@ -111,7 +101,7 @@ Trust Google for app permissions When disabled, the user is asked before an app\'s authorization request is sent to Google. Some applications will fail to use the Google account if this is disabled. Allow apps to find accounts - When enabled, all applications on this device will be able to see email address of your Google Accounts without prior authorization. + Als deze optie is ingeschakeld, kunnen alle applicaties op dit apparaat e-mailadressen van uw Google-accounts zien zonder voorafgaande toestemming. Authenticate with device registration When disabled, authentication requests won\'t be linked to the device registration, which may allow unauthorized devices to sign in, but may have unforeseen consequences. Strip device name for authentication @@ -124,7 +114,7 @@ Status More Google Accounts - Add and manage Google accounts + Google-accounts toevoegen en beheren Settings Accounts Account @@ -137,8 +127,8 @@ Confirm new apps Ask before registering a new app to receive push notifications Ping interval: <xliff:g example=10 minutes xmlns:xliff=urn:oasis:names:tc:xliff:document:1.2>%1$s</xliff:g> - About microG Services - Version information and used libraries + Over microG Diensten + Versie informatie en gebruikte bibliotheken Error unregistering No longer installed Unregister @@ -229,4 +219,14 @@ Some apps may require you to also enable license verification to verify your purchases. %1$s wil het volgende: %1$s wil het volgende gebruiken: + microG Beperkte diensten + Google accountmeldingen + Geeft een melding wanneer een van uw Google-accounts extra moet worden ingesteld voordat deze kan worden gebruikt of wanneer een account niet compatibel is met microG. + Accountactie vereist + <b><xliff:g example=“F-Droid”>%1$s</xliff:g></b> geprivilegieerde toegang toestaan tot <xliff:g example=“account@example.com”>%2$s</xliff:g>? + <b><xliff:g example=“F-Droid”>%1$s</xliff:g></b> wants to access your account as if it was <b><xliff:g example=“F-Droid”>%2$s</xliff:g> by <xliff:g example=“F-Droid Inc.”>%3$s</xliff:g></b>. This might grant it privileged access to your account. + Voer de volgende stappen uit om uw Google-account %s op dit apparaat te kunnen gebruiken. + Je apparaat moet zich minstens één keer registreren bij Google.\n\nU kunt de registratie van Google-apparaten uitschakelen nadat de account is ingesteld. + Cloud Messaging inschakelen + U kunt Aanmelden met Google beheren in uw Google-accounts. \ No newline at end of file diff --git a/play-services-core/src/main/res/values-pl/strings.xml b/play-services-core/src/main/res/values-pl/strings.xml index 354b859b7d..73e6431e2a 100644 --- a/play-services-core/src/main/res/values-pl/strings.xml +++ b/play-services-core/src/main/res/values-pl/strings.xml @@ -189,7 +189,7 @@ Spróbuj ponownie później." Skopiowano do schowka! Aplikacje korzystające z powiadomień ‘push‘ Własny: %s - Aplikacja na twoim urządzeniu próbuje zalogować się na konto Google. \u0020Jeśli było to zamierzone, użyj przycisku Zaloguj, aby połączyć się ze stroną logowania Google, w przeciwnym wypadku, użyj przycisku Anuluj, aby wrócić z powrotem do aplikacji, która spowodowała wyświetlenie tego okna dialogowego. + Aplikacja na twoim urządzeniu próbuje zalogować się na konto Google.\n\nJeśli było to zamierzone, użyj przycisku Zaloguj, aby połączyć się ze stroną logowania Google, w przeciwnym wypadku, użyj przycisku Anuluj, aby wrócić z powrotem do aplikacji, która spowodowała wyświetlenie tego okna dialogowego. Sprawdź usługę ReCAPTCHA Aby kontynuować, microG udostępni aplikacji %1$s nazwę, adres e-mail i zdjęcie profilowe twojego konta Google. Uwierzytelniaj za pomocą rejestracji urządzenia @@ -254,4 +254,27 @@ Spróbuj ponownie później." %1$s chce uzyskać dostęp do konta tak, jakby było %2$s autorstwa %3$s. Może to zapewnić mu uprzywilejowany dostęp do konta użytkownika. Automatycznie dodawaj bezpłatne aplikacje do biblioteki Darmowe aplikacje mogą sprawdzać, czy zostały pobrane ze Sklepu Google Play. Automatycznie dodawaj darmowe aplikacje do biblioteki konta, aby zawsze sprawdzać wszystkie aktualnie dostępne darmowe aplikacje. + Ograniczone usługi microG + Rozumiem + Korzystasz z ograniczonych usług microG. W przeciwieństwie do zwykłych usług microG, ten wariant działa tylko z aplikacjami korzystającymi z bibliotek microG, a nie z tych w Google Play. Oznacza to, że większość aplikacji zignoruje te usługi. + Włącz rejestrację urządzenia + Zezwalaj na Cloud Messaging dla microG + Skonfiguruj bezpieczną blokadę ekranu + Alerty dotyczące konta Google + Zgodnie z twoimi preferencjami, microG potrzebuje twojej zgody, zanim będzie mogło zarejestrować się w usłudze Cloud Messaging. + Zakończ konfigurację konta Google + Powiadamia, gdy jedno z kont Google wymaga dodatkowej konfiguracji przed użyciem lub gdy konto jest niekompatybilne z microG. + Wymagane działanie na koncie + Konto Google wymaga dodatkowej konfiguracji. + Wykonaj następujące kroki, aby móc korzystać z konta Google %s na tym urządzeniu. + Cloud Messaging możesz wyłączyć po zakończeniu konfiguracji konta. + Urządzenie musi zarejestrować się w Google przynajmniej raz.\n\nRejestrację urządzenia Google można wyłączyć po zakończeniu konfiguracji konta. + Włącz Cloud Messaging + Twoje konto Google jest zarządzane przez twoje miejsce pracy lub instytucję edukacyjną. Administrator zdecydował, że urządzenia wymagają bezpiecznej blokady ekranu przed uzyskaniem dostępu do danych konta.\n\nUstaw hasło, kod PIN lub wzór blokady ekranu. + Dotknij, aby wykonać etap + Etap zakończony + Zakończ + Włącz dostarczanie zasobów na żądanie + Google Play Asset Delivery + Pobieranie dodatkowych zasobów na żądanie aplikacji korzystających z funkcji Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-pt-rBR/strings.xml b/play-services-core/src/main/res/values-pt-rBR/strings.xml index 24b51e348d..2fa1d08737 100644 --- a/play-services-core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-core/src/main/res/values-pt-rBR/strings.xml @@ -167,7 +167,7 @@ Isso pode demorar alguns minutos." Permitir iniciar sessão ao %1$s Para continuar, o microG irá compartilhar o nome, endereço de email, e foto de perfil de sua conta do Google com %1$s. Feedback não disponível no momento - Um app no seu dispositivo está tentando fazer login numa conta do Google. \u0020Se isso for intencional, use o botão Iniciar sessão para se conectar a página de login do Google, se não, toque em Cancelar para voltar para o aplicativo que fez com que este diálogo aparecesse. + Um app no seu dispositivo está tentando fazer login numa conta do Google.\n\nSe isso for intencional, use o botão Iniciar sessão para se conectar a página de login do Google, se não, toque em Cancelar para voltar para o aplicativo que fez com que este diálogo aparecesse. provisionar serviços microG SafetyNet do Google Serviços do Google @@ -260,4 +260,27 @@ Isso pode demorar alguns minutos." %1$s deseja acessar sua conta como se fosse %2$s por %3$s. Isto pode concede-lô acesso privilegiado à sua conta. Automaticamente adicionar apps grátis à biblioteca Apps grátis podem verificar que foram baixados da Google Play. Automaticamente adicione apps grátis à sua biblioteca para que essas verificações passem pra você. + Serviços Limitados microG + Você está usando os Serviços Limitados microG. Diferente dos Serviços microG comum, esta variante só funciona com apps que usam as bibliotecas do microG, não aquelas do Google Play. Isto significa que a maioria dos aplicativos irão ignorar estes serviços. + Eu entendo + Complete os seguintes passos para poder usar sua conta Google %s neste dispositivo. + Permitir Cloud Messaging para o microG + Configurar bloqueio de tela seguro + Termine de configurar sua conta Google + Alertas da conta Google + Notifica quando uma das suas contas Google precisa de configuração adicional antes que seja usada ou quando uma conta é incompatível com o microG. + Ação de conta necessária + Sua conta Google precisa de configuração adicional. + Ativar registro do dispositivo + Seu dispositivo precisa registrar-se ao Google pelo menos uma vez.\n\nVocê pode desativar o registro do dispositivo Google após a configuração da conta estar completa. + Ativar o Cloud Messaging + Você pode desativar o Cloud Messaging (também chamado de notificações push) depois que configuração da conta esteja completa. + De acordo com suas preferências, o microG precisa de permissão de você antes que possa se registrar para o Cloud Messaging. + Sua conta Google é gerenciada pelo seu lugar de trabalho ou instituição escolar. Seu administrador decidiu que dispositivos devem ter um bloqueio de tela seguro antes que possam acessar dados da conta.\n\nPor favor configure uma senha, PIN, ou um padrão como seu bloqueio de tela. + Clique para executar este passo + Terminar + Passo completo + Asset Delivery da Google Play + Ativar entrega de recursos em-demanda + Baixar recursos adicionais quando solicitados por apps que usam o Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-ro/strings.xml b/play-services-core/src/main/res/values-ro/strings.xml index a32dbd43d9..c388b6ce8f 100644 --- a/play-services-core/src/main/res/values-ro/strings.xml +++ b/play-services-core/src/main/res/values-ro/strings.xml @@ -55,7 +55,7 @@ \n \nAcest lucru poate dura câteva secunde. Permite și distribuie - O aplicație de pe dispozitivul tău încearcă să se conecteze la un cont Google. \u0020Dacă acest lucru a fost intenționat, utilizează butonul Conectare pentru a te conecta la pagina de conectare Google, dacă nu, apasă Anulare pentru a reveni la aplicația care a generat acest dialog. + O aplicație de pe dispozitivul tău încearcă să se conecteze la un cont Google. \n\nDacă acest lucru a fost intenționat, utilizează butonul Conectare pentru a te conecta la pagina de conectare Google, dacă nu, apasă Anulare pentru a reveni la aplicația care a generat acest dialog. Configurează serviciile microG. Importă profil personalizat Tipul solicitării @@ -256,4 +256,27 @@ Permiți pentru %1$s acces privilegiat la %2$s\? Adaugă automat aplicațiile gratuite în bibliotecă Aplicațiile gratuite pot verifica dacă au fost descărcate de pe Google Play. Adaugă automat aplicațiile gratuite în biblioteca contului pentru a trece întotdeauna verificarea pentru toate aplicațiile gratuite disponibile în prezent. + Servicii limitate microG + Utilizezi serviciile limitate microG. Spre deosebire de serviciile microG obișnuite, această versiune funcționează numai cu aplicațiile care folosesc biblioteci microG, nu cu cele de pe Google Play. Aceasta înseamnă că majoritatea aplicațiilor vor ignora aceste servicii. + Am înțeles + Alerte pentru contul Google + Contul Google necesită o configurare suplimentară. + Finalizează configurarea contului Google + Activează înregistrarea dispozitivului + Dispozitivul trebuie să se înregistreze la Google cel puțin o dată.\n\nPoți dezactiva înregistrarea dispozitivului Google după finalizarea configurării contului. + Permite mesageria în cloud pentru microG + În funcție de preferințele tale, microG are nevoie de permisiunea ta înainte de a se putea înregistra pentru mesageria în cloud. + Clic pentru a efectua pasul + Pas finalizat + Finalizare + Configurează blocarea securizată a ecranului + Notifică atunci când unul dintre conturile Google necesită o configurare suplimentară înainte de a putea fi utilizat sau când un cont este incompatibil cu microG. + Este necesară acțiunea în cont + Parcurge următorii pași pentru a putea folosi contul Google %s pe acest dispozitiv. + Poți dezactiva mesageria în cloud după finalizarea configurării contului. + Activează mesageria în cloud + Contul Google este gestionat de locul de muncă sau de instituția de învățământ. Administratorul a decis că dispozitivele au nevoie de o blocare securizată a ecranului înainte de a putea accesa datele contului.\n\nConfigurează o parolă, un cod PIN sau un model de deblocare al ecranului. + Livrare de active Google Play + Activează livrarea de active la cerere + Descarcă materiale suplimentare atunci când sunt solicitate de aplicațiile care folosesc Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-sr/permissions.xml b/play-services-core/src/main/res/values-sr/permissions.xml index abc67e6375..e1e97505fd 100644 --- a/play-services-core/src/main/res/values-sr/permissions.xml +++ b/play-services-core/src/main/res/values-sr/permissions.xml @@ -77,7 +77,7 @@ Гласовна претрага Дозвољава апликацији да приступа Google групама преко било ког повезаног Google налога. Google мапе - Управљање конфигурацијом Ad Exchange налога купца + Управљање подешавањем Ad Exchange налога купца Размена порука у облаку за Chrome Дозвољава апликацији да приступи AdSense-у преко било ког повезаног Google налога. Преглед и управљање вашим Ad Exchange подацима diff --git a/play-services-core/src/main/res/values-sr/plurals.xml b/play-services-core/src/main/res/values-sr/plurals.xml index e50c62186c..9afb42aefc 100644 --- a/play-services-core/src/main/res/values-sr/plurals.xml +++ b/play-services-core/src/main/res/values-sr/plurals.xml @@ -30,8 +30,8 @@ Захтевај недостајуће дозволе - %1$d бекенд конфигурисан - %1$d бекенда конфигурисана - %1$d бекендова конфигурисано + %1$d бекенд подешен + %1$d бекенда подешена + %1$d бекендова подешено \ No newline at end of file diff --git a/play-services-core/src/main/res/values-sr/strings.xml b/play-services-core/src/main/res/values-sr/strings.xml index 32b7f9ce73..d39ad845a0 100644 --- a/play-services-core/src/main/res/values-sr/strings.xml +++ b/play-services-core/src/main/res/values-sr/strings.xml @@ -123,7 +123,7 @@ Google SafetyNet је систем сертификације уређаја, који осигурава да је уређај правилно обезбеђен и компатибилан са Android CTS. Неке апликације користе SafetyNet из безбедносних разлога или као предуслов за заштиту од неовлашћеног приступа. \n \nmicroG GmsCore садржи бесплатну имплементацију SafetyNet-а, али званични сервер захтева да SafetyNet захтеви буду потписани коришћењем власничког система DroidGuard. - читање конфигурације Google услуге + читање подешавања Google услуге Пријави се Увезите профил уређаја из фајла Копирај JSON JWS податке @@ -131,7 +131,7 @@ Када је омогућено, све апликације на овом уређају ће моћи да виде имејл адресу ваших Google налога без претходног овлашћења. Савет %s секунди - Конфигурација + Подешавање Google услуге Неважећи JSON Очисти недавне захтеве @@ -165,7 +165,7 @@ Последња употреба: %1$s Будите упитани пре регистровања нове апликације да бисте примали push-обавештења Покрени апликацију при push-обавештењу - Дозвољава апликацији да конфигурише microG услуге без интеракције корисника + Дозвољава апликацији да подешава microG услуге без интеракције корисника ReCaptcha: %s Примај push-обавештења Прођени сви тестови @@ -192,9 +192,7 @@ Копирано у привремену меморију! Апликације које користе push-обавештења Прилагођено: %s - Апликација на вашем уређају покушава да се пријави на Google налог. -\n -\nАко је ово било намерно, користите дугме Пријави се да бисте се повезали са Google страницом за пријављивање, ако није, притисните Откажи да бисте се вратили на апликацију која је изазвала да се овај дијалог прикаже. + Апликација на вашем уређају покушава да се пријави на Google налог. \n\nАко је ово било намерно, користите дугме Пријави се да бисте се повезали са Google страницом за пријављивање, ако није, притисните Откажи да бисте се вратили на апликацију која је изазвала да се овај дијалог прикаже. Тестирај ReCAPTCHA Да бисте наставили, microG ће делити име, имејл адресу и слику профила вашег Google налога са %1$s . Аутентификуј регистрацијом уређаја @@ -260,4 +258,27 @@ %1$s жели да приступе вашем налогу као да је %2$s од %3$s. Ово би му могло дати привилеговани приступ вашем налогу. Бесплатне апликације могу да провере да ли су преузете са Google Play-а. Аутоматски додајте бесплатне апликације у библиотеку налога да бисте увек прошли проверу за све бесплатне апликације које су вам тренутно доступне. Аутоматски додај бесплатне апликације у библиотеку + microG ограничене услуге + Разумем + Користите microG ограничене услуге. За разлику од уобичајених microG услуга, ова верзија функционише само са апликацијама које користе microG библиотеке, а не са онима на Google Play-у. То значи да ће већина апликација занемарити ове услуге. + Неопходна радња на налогу + Вашем Google налогу је потребно додатно подешавање. + Омогући регистрацију уређаја + Омогући размену поруку у облаку + Омогући размену порука у облаку за microG + Додирните да бисте извршили корак + Корак завршен + Заврши + Завршите следеће кораке да бисте могли да користите свој Google налог %s на овом уређају. + Обавештења Google налога + Обавештава када неки од ваших Google налога захтева додатно подешавање пре него што се може користити или када налог није компатибилан са microG-ом. + Ваш уређај мора да се региструје на Google бар једном.\n\nМожете да онемогућите Google регистрацију уређаја након што је подешавање налога завршено. + Завршите подешавање свог Google налога + Према вашим преференцама, microG-у је потребна ваша дозвола да би могао да се региструје за размену порука у облаку. + Можете онемогућити размену порука у облаку након што је подешавање налога завршено. + Подесите безбедно закључавање екрана + Вашим Google налогом управља ваше радно место или образовна институција. Ваш администратор је одлучио да је уређајима потребно безбедно закључавање екрана да би могли да приступе подацима налога.\n\nПодесите лозинку, PIN или шаблон за закључавање екрана. + Омогући испоруку средстава на захтев + Преузмите додатна средства када то затраже апликације које користе Play Asset Delivery + Google Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-sv/strings.xml b/play-services-core/src/main/res/values-sv/strings.xml index a841de3349..97c364822d 100644 --- a/play-services-core/src/main/res/values-sv/strings.xml +++ b/play-services-core/src/main/res/values-sv/strings.xml @@ -114,7 +114,7 @@ %1$s kräver din autentisering för att komma åt ditt Google-konto. microG-tjänster Systemet ger signaturspoofingtillstånd: - En app på din enhet försöker logga in på ett Google-konto. \u0020Om detta var avsiktligt, använd knappen Logga in för att ansluta till Googles inloggningssida, om inte, tryck på Avbryt för att gå tillbaka till programmet som gjorde att den här dialogrutan dök upp. + En app på din enhet försöker logga in på ett Google-konto.\n\nOm detta var avsiktligt, använd knappen Logga in för att ansluta till Googles inloggningssida, om inte, tryck på Avbryt för att gå tillbaka till programmet som gjorde att den här dialogrutan dök upp. Batterioptimering ignorerad: För att fortsätta kommer microG att dela namn, e-postadress och profilbild för ditt Google-konto med %1$s. Autentisering krävs @@ -242,4 +242,27 @@ %1$s vill komma åt ditt konto som om det var %2$s by %3$s. Detta kan ge F-Droid privilegierad tillgång till ditt konto. Lägg automatiskt till gratisappar i biblioteket Gratisappar kan kontrollera om de har laddats ner från Google Play. Lägg automatiskt till gratisappar i ditt kontobibliotek för att alltid klara kontrollen för alla gratisappar som för närvarande är tillgängliga för dig. + Jag förstår + microG Limited Services + Du använder microG Limited Services. Till skillnad från de vanliga mikroG-tjänsterna fungerar denna version endast med appar som använder mikroG-bibliotek, inte de på Google Play. Det innebär att de flesta appar ignorerar dessa tjänster. + Ditt Google-konto behöver ytterligare inställning. + Tillåt Cloud Messaging för microG + Konfigurera säkert skärmlås + Tryck för att utföra steg + Kontoåtgärder krävs + Google kontomeddelanden + Slutför följande steg för att kunna använda ditt Google-konto %s på den här enheten. + Meddelar när en av dina Google-konton kräver ytterligare inställning innan det kan användas eller när ett konto är oförenligt med microG. + Slutför installation av ditt Google-konto + Aktivera enhetsregistrering + Din enhet måste registreras hos Google minst en gång.\n\nDu kan inaktivera Google-enhetsregistrering efter att kontoinställningen är klar. + Aktivera Cloud Messaging + Du kan inaktivera Cloud Messaging efter att kontoinställningen är klar. + Enligt dina inställningar, behöver microG tillstånd från dig innan det kan registreras för Cloud Messaging. + Slutför + Ditt Google-konto hanteras av din arbetsplats eller utbildningsinstitution. Din administratör bestämde att enheter behöver ett säkert skärmlås innan de kan komma åt kontodata.\n\nAnge ett lösenord, PIN eller mönsterlåsskärm. + Steg färdigställt + Google Play Asset Delivery + Aktivera tillgångsleverans på begäran + Ladda ner fler tillgångar, på begäran av appar som använder Google Play Asset Delivery \ No newline at end of file diff --git a/play-services-core/src/main/res/values-th/permissions.xml b/play-services-core/src/main/res/values-th/permissions.xml new file mode 100644 index 0000000000..35f784b802 --- /dev/null +++ b/play-services-core/src/main/res/values-th/permissions.xml @@ -0,0 +1,159 @@ + + + บริการทั้งหมดของ Google + อนุญาตให้แอปเข้าถึงบริการ Google ทั้งหมดที่เชื่อมโยงผ่านทางบัญชีของ Google + บริการแอนดรอยด์ + อนุญาตให้แอปเข้าถึง AdSense ผ่านบัญชี Google ที่เกี่ยวข้อง + Google Ads + Google App Engine + อนุญาตให้แอปเข้าถึง Google App Engine ผ่านทางบัญชี Google ที่เชื่อมโยง + AdSense + Blogger + ปฏิทิน Google + อนุญาตให้แอปเข้าถึง Google Calendar ผ่านทางบัญชี Google ที่เชื่อมโยง + รายชื่อ + ดอดจ์บอล + อนุญาตให้แอปเข้าถึง Dodgeball ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึงบริการ Android ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง Google Ads ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง Blogger ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึงรายชื่อผ่านบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง Google Finance ผ่านบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง Google Base ผ่านทางบัญชี Google ที่เชื่อมโยง + Google Voice + อนุญาตให้แอปเข้าถึง Google Voice ผ่านทางบัญชี Google ที่เชื่อมโยง + Google Finance + Google Base + Google Health + อนุญาตให้แอปเข้าถึง Google Health ผ่านทางบัญชี Google ที่เชื่อมโยง + iGoogle + Google News + อนุญาตให้แอปเข้าถึง Google News ผ่านทางบัญชี Google ที่เชื่อมโยง + Orkut + อนุญาตให้แอปเข้าถึง Orkut ผ่านทางบัญชี Google ที่เชื่อมโยง + Google Book Search + บัญชี Google Checkout + อนุญาตให้แอปเข้าถึงบัญชี Google Checkout ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึงบัญชี Google Checkout QA ผ่านทางบัญชี Google ที่เชื่อมโยง + บัญชี Google Checkout Sandbox + เครื่องมือสำหรับผู้ดูแลเว็บของ Google + อนุญาตให้แอปเข้าถึง Google Webmaster Tools ผ่านทางบัญชี Google ที่เชื่อมโยง + การค้นหาด้วยเสียง + การจดจำเสียงพูดส่วนบุคคล + Google Talk + อนุญาตให้แอปเข้าถึง Google Talk ผ่านทางบัญชี Google ที่เชื่อมโยง + Google Wi-Fi + อนุญาตให้แอปเข้าถึง Google Wi-Fi ผ่านบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง Google Spreadsheets ผ่านทางบัญชี Google ที่เชื่อมโยง + Google Docs + YouTube + อนุญาตให้แอปเข้าถึง YouTube ผ่านทางบัญชี Google ที่เชื่อมโยง + ชื่อผู้ใช้ YouTube + Google Groups + อนุญาตให้แอปเข้าถึง Google Groups ผ่านทางบัญชี Google ที่เกี่ยวข้อง + อนุญาตให้แอปเข้าถึง Google Mail ผ่านทางบัญชี Google ที่เชื่อมโยง + Google Notebook + อนุญาตให้แอปเข้าถึง Google Notebook ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง Google Book Search ผ่านทางบัญชี Google ที่เชื่อมโยง + บัญชี QA ของ Google Checkout + อนุญาตให้แอปเข้าถึงบัญชี Google Checkout Sandbox ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึงการค้นหาด้วยเสียงผ่านบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึงการจดจำคำพูดส่วนบุคคลผ่านทางบัญชี Google ที่เชื่อมโยง + Google Spreadsheets + อนุญาตให้แอปเข้าถึง Google Docs ผ่านทางบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึงชื่อผู้ใช้ YouTube ที่ใช้กับบัญชี Google ที่เชื่อมโยง + อนุญาตให้แอปเข้าถึง iGoogle ผ่านทางบัญชี Google ที่เชื่อมโยง + ฮอตสปอต + อนุญาตให้แอปเข้าถึง HotSpot ผ่านทางบัญชี Google ที่เชื่อมโยง + Knol + อนุญาตให้แอปเข้าถึง สารานุกรมออนไลน์ ผ่านบัญชี Google ที่เชื่อมโยง + อัลบั้มเว็บ Picasa + อนุญาตให้แอปเข้าถึง Picasa Web Albums ผ่านทางบัญชี Google ที่เชื่อมโยง + แผนที่ Google + อนุญาตให้แอปเข้าถึง Google Maps ผ่านบัญชี Google ที่เชื่อมโยง + Google Mail + ดูประวัติกิจกรรมของ Google Apps ของคุณ + จัดการการกำหนดค่าบัญชีผู้ซื้อ Ad Exchange ของคุณ + ดูข้อมูล Ad Exchange ของคุณ + ดูข้อมูล Ad Exchange ของคุณ + นอกเหนือจากขอบเขตการอ่าน/เขียน OAuth โดยรวมแล้ว ให้ใช้ขอบเขต OAuth แบบอ่านอย่างเดียวเมื่อดึงข้อมูลของลูกค้า + จัดการบัญชีบล็อกเกอร์ของคุณ + ดูข้อมูลเมตาสำหรับไฟล์และเอกสารใน Google Drive ของคุณ + ดูและจัดการงาน Google Maps Coordinate ของคุณ + ดูและจัดการข้อมูลการพิมพ์บนคลาวด์ของ Google ของคุณ + ดูและจัดการไฟล์ Google Drive ที่คุณเปิดหรือสร้างด้วยแอปนี้ + ดูข้อมูลของคุณใน Google Cloud Storage + ดูและจัดการไฟล์และเอกสารใน Google Drive ของคุณ + ดูที่อยู่อีเมลของคุณ + Google Maps Tracks API, ขอบเขตนี้อนุญาตให้เข้าถึงแบบอ่านและเขียนข้อมูลของโครงการของคุณได้ + ดูข้อมูลพื้นฐานเกี่ยวกับบัญชีของคุณ + การจัดการของคุณ วิดีโอ YouTube + ดูและจัดการข้อมูลโฮสต์ AdSense และบัญชีที่เกี่ยวข้องของคุณ + ดูข้อมูล AdSense ของคุณ + ดูและจัดการข้อมูล AdSense ของคุณ + ขอบเขตการดูแลระบบแอปเอนจิ้น + การเข้าถึงแบบอ่านและเขียนไปยัง Groups Migration API + ดูและจัดการการตั้งค่าของกลุ่ม Google Apps + การเข้าถึงแบบอ่าน/เขียนสำหรับ License Manager API + สำหรับผู้ดูแลระบบตัวแทนจำหน่ายและผู้ใช้มีสิทธิ์การอ่าน/เขียนเมื่อทำการทดสอบในแซนด์บ็อกซ์ของ API หรือมีสิทธิ์การอ่าน/เขียนเมื่อเรียกการดำเนินการ API โดยตรง + การเข้าถึง API การตรวจสอบผู้ดูแลระบบแบบอ่านอย่างเดียว + ขอบเขตการใช้งานบริการ App State + ดูข้อมูลของคุณใน Google BigQuery + ดูและจัดการข้อมูลของคุณใน Google BigQuery + จัดการบัญชีบล็อกเกอร์ของคุณ + จัดการหนังสือของคุณ + จัดการปฏิทินของคุณ + ดูปฏิทินของคุณ + ดูทรัพยากร Google Compute Engine ของคุณ + ดูและจัดการทรัพยากร Google Compute Engine ของคุณ + ดูงาน Google Coordinate ของคุณ + จัดการข้อมูลและสิทธิ์ของคุณใน Google Cloud Storage + จัดการข้อมูลของคุณใน Google Cloud Storage + ดูและจัดการรายงาน DoubleClick สำหรับผู้โฆษณา + อนุญาตให้เข้าถึงโฟลเดอร์ข้อมูลแอปพลิเคชัน + ดูแอป Google Drive ของคุณ + ขอบเขตพิเศษที่ใช้เพื่อให้ผู้ใช้สามารถอนุมัติการติดตั้งแอปได้ + ดูไฟล์และเอกสารใน Google Drive ของคุณ + ปรับเปลี่ยนพฤติกรรมสคริปต์ Google Apps ของคุณ + ดูบัญชี Freebase ของคุณ + จัดการ URL สั้นของ goo.gl ของคุณ + ดูงานของคุณ + จัดการงานของคุณ + ใช้ภารกิจจากคิวภารกิจของคุณ + จัดการงานของคุณ + การจัดการของคุณ บัญชี YouTube + ดูและจัดการสินทรัพย์ของคุณและเนื้อหาที่เกี่ยวข้องกับ YouTube + ดูบัญชี YouTube ของคุณ + อ่านทรัพยากรทั้งหมดและข้อมูลเมตาของทรัพยากรเหล่านั้น—ไม่มีการดำเนินการเขียน + การเข้าถึงนักพัฒนา Android ของ Google Play + ดูข้อมูล Google Analytics ของคุณ + ดูและจัดการข้อมูล Google Analytics ของคุณ + ลงชื่อเข้าใช้ Firebase ด้วยบัญชีของคุณ + จัดการตารางฟิวชันของคุณ + ดูตารางฟิวชันของคุณ + ขอบเขตการเข้าถึงข้อมูลจาก Google Play Games + จัดการข้อมูล GAN ของคุณ + ดูข้อมูล GAN ของคุณ + CloudMessaging สำหรับ Chrome + ขอบเขตของ Glass timeline + สร้าง อ่าน อัปเดต และลบฉบับร่าง ส่งข้อความและฉบับร่าง + การดำเนินการอ่าน/เขียนทั้งหมด ยกเว้นการลบเธรดและข้อความทันทีและถาวร โดยข้ามถังขยะ + จัดการตำแหน่งที่ดีที่สุดที่มีอยู่และประวัติตำแหน่งของคุณ + จัดการตำแหน่งระดับเมืองและประวัติตำแหน่งของคุณ + จัดการตำแหน่งที่ดีที่สุดที่มีอยู่ของคุณ + จัดการตำแหน่งระดับเมืองของคุณ + ดูและจัดการข้อมูล Google Maps Engine ของคุณ + ดูข้อมูล Google Maps Engine ของคุณ + ดูและจัดการ Google Maps ของคุณสำหรับประสบการณ์มือถือ + จัดการกิจกรรม Orkut ของคุณ + ดูข้อมูล Orkut ของคุณ + ทราบชื่อของคุณ ข้อมูลพื้นฐาน และรายชื่อบุคคลที่คุณเชื่อมต่อด้วยบน Google+ + รู้ว่าคุณเป็นใครบน Google + จัดการข้อมูลของคุณใน Google Prediction API + ดูข้อมูลผลิตภัณฑ์ของคุณ + จัดการรายการไซต์และโดเมนที่คุณควบคุม + จัดการการตรวจสอบไซต์ใหม่ของคุณด้วย Google + การเข้าถึงแบบอ่าน/เขียนสำหรับ Shopping Content API + ดูรายงานทางการเงินของ YouTube Analytics สำหรับเนื้อหา YouTube ของคุณ + ดูรายงาน YouTube Analytics สำหรับเนื้อหา YouTube ของคุณ + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-th/plurals.xml b/play-services-core/src/main/res/values-th/plurals.xml new file mode 100644 index 0000000000..503e96b330 --- /dev/null +++ b/play-services-core/src/main/res/values-th/plurals.xml @@ -0,0 +1,15 @@ + + + + %1$d กำหนดค่า แบ็คเอนด์แล้ว + + + %1$d ลงทะเบียนแอปแล้ว + + + การอนุญาตที่จำเป็นในการทำงานอย่างถูกต้องของบริการ microG ไม่ผ่าน + + + การร้องขอการอนุญาตผิดพลาด + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-th/strings.xml b/play-services-core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..9831d6ea86 --- /dev/null +++ b/play-services-core/src/main/res/values-th/strings.xml @@ -0,0 +1,252 @@ + + + การตั้งค่า microG + รอสักครู่… + Google + %1$s ต้องการใช้: + ผู้จัดการบัญชี Google + เสียใจ… + ลงชื่อเข้าใช้ + มีปัญหาในการสื่อสารกับเซิร์ฟเวอร์ของ Google\n\nลองอีกครั้งในภายหลัง + อุปกรณ์ของคุณกำลังติดต่อ Google เพื่อบันทึกข้อมูลลงในบัญชีของคุณ\n\nอาจใช้เวลาสองสามนาที + อนุญาต + บริการของ microG + ตั้งค่าบริการ microG + การดำเนินการต่อถือว่าคุณยินยอมให้แอปนี้และ Google ใช้ข้อมูลของคุณตามข้อกำหนดในการให้บริการและนโยบายความเป็นส่วนตัวของแต่ละแอป + %1$s ต้องการ: + แอปบนอุปกรณ์ของคุณกำลังพยายามลงชื่อเข้าใช้บัญชี Google\n\nหากตั้งใจ ให้ใช้ปุ่ม ลงชื่อเข้าใช้ เพื่อเชื่อมต่อกับหน้าลงชื่อเข้าใช้ของ Google หากไม่ได้ตั้งใจ ให้กด ยกเลิก เพื่อกลับไปยังแอปพลิเคชันที่ทำให้กล่องโต้ตอบนี้ปรากฏขึ้น + อุปกรณ์ของคุณกำลังสร้างการเชื่อมต่อกับเซิร์ฟเวอร์ของ Google เพื่อลงชื่อเข้าใช้ให้คุณ\n\nอาจใช้เวลาสักครู่ + คุณไม่ได้เชื่อมต่อกับเครือข่าย\n\nนี่อาจเป็นปัญหาชั่วคราวหรืออุปกรณ์ Android ของคุณอาจไม่มีการให้บริการข้อมูล ลองอีกครั้งเมื่อเชื่อมต่อกับเครือข่ายมือถือหรือเชื่อมต่อกับเครือข่าย Wi-Fi + microG Limited Services + ปฏิเสธ + จำเป็นต้องมีการพิสูจน์ตัวตน + %1$s ต้องได้รับอนุญาตจากคุณจึงจะเข้าถึงบัญชี Google ของคุณได้ + %1$s ต้องการเข้าถึงบัญชีของคุณราวกับว่าเป็น %2$s โดย %3$s การกระทำดังกล่าวอาจทำให้บัญชีดังกล่าวได้รับสิทธิ์พิเศษในการเข้าถึงบัญชีของคุณ + เลือกบัญชี + เพื่อดำเนินการต่อ %1$s + เพิ่มบัญชีอื่น + อนุญาตให้คุณลงชื่อเข้าใช้ %1$s + อนุญาตและแบ่งปัน + ให้บริการไมโครจี + อนุญาตให้แอปกำหนดค่าบริการ microG โดยไม่ต้องมีการโต้ตอบจากผู้ใช้ + ความเร็วรถ + เข้าถึงความเร็วรถของคุณ + ข้อมูลรถยนต์ + เข้าถึงข้อมูลรถของคุณ + ระดับน้ำมันเชื้อเพลิงรถยนต์ + ระยะทางการใช้รถ + เข้าถึงข้อมูลไมล์รถของคุณ + ช่องทางจำหน่ายรถยนต์ + การลงทะเบียนอุปกรณ์ Google + การส่งข้อความบนคลาวด์ + บริการ Play Store + Google Play Games + %1$s ต้องการใช้ Play Games + หากต้องการใช้ Play Games จำเป็นต้องติดตั้งแอป Google Play Games แอปพลิเคชันอาจทำงานต่อไปโดยไม่มี Play Games แต่ก็เป็นไปได้ที่แอปพลิเคชันจะทำงานผิดปกติ + เลือกสถานที่ + ตัวเลือกสถานที่ยังไม่พร้อมใช้งาน + สถานที่ใกล้เคียง + (%1$.7f, %2$.7f) + เครือข่ายมือถือ + โรมมิ่ง + เครือข่ายอื่นๆ + รองรับการปลอมแปลงลายเซ็นดิจิตอล + แพ็คเกจที่ติดตั้ง + ระบบ + ระบบมีรองรับการปลอมลายเซ็นดิจิตอล: + อนุญาตให้ %1$s เข้าถึง %2$s อย่างมีสิทธิพิเศษหรือไม่? + ในการดำเนินการต่อ microG จะแบ่งปันชื่อ ที่อยู่อีเมล และรูปโปรไฟล์ของบัญชี Google ของคุณกับ %1$s + เข้าถึงข้อมูลระดับน้ำมันเชื้อเพลิงของรถของคุณ + เข้าถึงช่องทางผู้จำหน่ายรถของคุณเพื่อแลกเปลี่ยนข้อมูลเฉพาะรถ + Google SafetyNet + เลือกตำแหน่งนี้ + บริการ microG: ขาดการอนุญาตให้ %1$s + Wi-Fi + ที่ติดตั้งไว้ %1$s เข้ากันไม่ได้ หรือการปลอมลายเซ็นไม่ได้เปิดใช้งาน โปรดตรวจสอบเอกสารเกี่ยวกับแอปพลิเคชันและ ROM ที่เข้ากันได้ + แตะที่นี่เพื่อปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่ หากไม่ทำเช่นนี้ อาจทำให้แอปพลิเคชันทำงานผิดปกติ + ติดตั้งแอปพลิเคชัน %1$s หรือแอปพลิเคชันที่เข้ากันได้ โปรดตรวจสอบเอกสารประกอบว่าแอปพลิเคชันใดที่เข้ากันได้ + ก่อนใช้แอปนี้ โปรดอ่าน %1$s และ %2$s + นโยบายความเป็นส่วนตัว + ข้อกําหนดในการให้บริการ + ฟังสถานะภายในของการออกอากาศ + อ่านการกําหนดค่า Google + ดักข้อความ C2DM + ส่งข้อความ C2DM ไปยังแอปอื่น ๆ + การแลกเปลี่ยนข้อความและรับการแจ้งเตือนจากเซิร์ฟเวอร์ Google + เข้าถึงบริการ Google + ROM ของคุณไม่มีการสนับสนุนดั้งเดิมสำหรับการปลอมลายเซ็น คุณยังสามารถใช้ Xposed หรือระบบอื่นเพื่อปลอมลายเซ็นได้ โปรดตรวจสอบเอกสารประกอบเกี่ยวกับ ROM ที่รองรับการปลอมลายเซ็นและวิธีใช้ microG บน ROM ที่ไม่รองรับ + ระบบให้สิทธิ์ในการปลอมลายเซ็น: + นี่เป็นตัวบ่งชี้ที่ชัดเจนว่า ROM รองรับการปลอมลายเซ็น แต่ต้องมีการดำเนินการเพิ่มเติมเพื่อเปิดใช้งาน โปรดตรวจสอบเอกสารเกี่ยวกับขั้นตอนที่อาจจำเป็น + ระบบปลอมลายเซ็น: + โปรดตรวจสอบเอกสารเกี่ยวกับขั้นตอนที่อาจจำเป็น + Play Services (GmsCore) + Play Store (Phonesky) + Services Framework (GSF) + %1$s ติดตั้งแล้ว: + %1$s มีลายเซ็นที่ถูกต้อง: + ละเว้นการเพิ่มประสิทธิภาพแบตเตอรี่: + เกี่ยวกับ + ส่วนประกอบ + การกําหนดค่า + Google Services + การบริการระบุตำแหน่ง + บริการ + ทดสอบ + เปิดใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่แล้ว + การแจ้งเตือนบัญชี Google + จำเป็นต้องดำเนินการกับบัญชี + แจ้งเตือนเมื่อบัญชี Google ของคุณต้องมีการตั้งค่าเพิ่มเติมก่อนจึงจะสามารถใช้งานได้ หรือเมื่อบัญชีเข้ากันไม่ได้กับ microG + บัญชี Google ของคุณต้องมีการตั้งค่าเพิ่มเติม + เสร็จสิ้นการตั้งค่าบัญชี Google ของคุณ + เปิดใช้งานการลงทะเบียนอุปกรณ์ + เปิดใช้งานการส่งข้อความบนคลาวด์ + คุณสามารถปิดการใช้งาน Cloud Messaging ได้หลังจากตั้งค่าบัญชีเสร็จสิ้น + อนุญาตให้มีการส่งข้อความบนคลาวด์สำหรับ microG + กำหนดค่าการล็อคหน้าจอที่ปลอดภัย + คลิกเพื่อดำเนินการขั้นตอน + ขั้นตอนเสร็จสมบูรณ์ + เสร็จ + การตั้งค่าบัญชี + ข้อมูลส่วนตัวและความเป็นส่วนตัว + การลงชื่อเข้าใช้และความปลอดภัย + เชื่อถือ Google สำหรับการอนุญาตสิทธิ์แอป + หากปิดใช้งาน ผู้ใช้จะถูกถามก่อนที่จะส่งคำขออนุญาตของแอปไปยัง Google แอปพลิเคชันบางตัวจะไม่สามารถใช้บัญชี Google ได้หากปิดใช้งานตัวเลือกนี้ + อนุญาตให้แอปค้นหาบัญชี + ยืนยันตัวตนด้วยการลงทะเบียนอุปกรณ์ + ถอดชื่ออุปกรณ์ออกเพื่อยืนยันตัวตน + ลงทะเบียนอุปกรณ์ของคุณกับบริการของ Google และสร้างตัวระบุอุปกรณ์เฉพาะ microG จะลบบิตที่ระบุอื่นๆ นอกเหนือจากชื่อบัญชี Google ของคุณออกจากข้อมูลการลงทะเบียน + Android ID + ไม่ได้ลงทะเบียน + ลงทะเบียนล่าสุด: %1$s + ลงทะเบียนอุปกรณ์ + สถานะ + มากกว่า + บัญชี Google + เพิ่มและจัดการบัญชี Google + ตั้งค่า + บัญชี + บัญชี + เพิ่มบัญชี Google + แอปที่ใช้ระบบ Cloud Messaging + รายการแอปที่ลงทะเบียนสำหรับ Cloud Messaging ในปัจจุบัน + ยืนยันแอปใหม่ + ถามก่อนลงทะเบียนแอพใหม่เพื่อรับการแจ้งเตือนแบบพุช + ดำเนินการตามขั้นตอนต่อไปนี้เพื่อจะใช้งานบัญชี Google %s ของคุณบนอุปกรณ์นี้ได้ + อุปกรณ์ของคุณจำเป็นต้องลงทะเบียนกับ Google อย่างน้อยหนึ่งครั้ง\n\nคุณสามารถปิดใช้งานการลงทะเบียนอุปกรณ์ Google ได้หลังจากตั้งค่าบัญชีเสร็จสิ้น + ตามการตั้งค่าของคุณ microG จะต้องได้รับอนุญาตจากคุณก่อนจึงจะสามารถลงทะเบียนสำหรับ Cloud Messaging ได้ + เมื่อเปิดใช้งานแล้ว แอปพลิเคชันทั้งหมดในอุปกรณ์นี้จะสามารถเห็นที่อยู่อีเมลของบัญชี Google ของคุณได้ โดยไม่ต้องได้รับอนุญาตก่อน + บัญชี Google ของคุณได้รับการจัดการโดยที่ทำงานหรือสถาบันการศึกษาของคุณ ผู้ดูแลระบบของคุณตัดสินใจว่าอุปกรณ์จำเป็นต้องมีการล็อกหน้าจอที่ปลอดภัยก่อนจึงจะสามารถเข้าถึงข้อมูลบัญชีได้\n\nโปรดตั้งรหัสผ่าน PIN หรือรูปแบบการล็อกหน้าจอ + เมื่อเปิดใช้งาน การร้องขอการตรวจสอบสิทธิ์จะไม่รวมชื่ออุปกรณ์ ซึ่งอาจทำให้อุปกรณ์ที่ไม่ได้รับอนุญาตลงชื่อเข้าใช้ได้ แต่ก็อาจส่งผลที่ไม่คาดคิดได้ + เมื่อปิดใช้งาน คำขอการตรวจสอบสิทธิ์จะไม่เชื่อมโยงกับการลงทะเบียนอุปกรณ์ ซึ่งอาจทำให้อุปกรณ์ที่ไม่ได้รับอนุญาตลงชื่อเข้าใช้ได้ แต่ก็อาจส่งผลที่ไม่คาดคิดได้ + ระยะเวลาเป็นวินาทีที่ระบบจะส่งสัญญาณไปยังเซิร์ฟเวอร์ของ Google การเพิ่มจำนวนนี้จะช่วยลดการใช้แบตเตอรี่ แต่ข้อความพุชอาจเกิดความล่าช้าได้\nไม่สนับสนุนอีกต่อไป แต่จะถูกแทนที่ในรุ่นถัดไป + ช่วงเวลาปิง: %1$s + คุณเปิดใช้งาน Cloud Messaging แต่มีการเพิ่มประสิทธิภาพแบตเตอรี่สำหรับบริการ microG หากต้องการให้การแจ้งเตือนแบบพุชมาถึงคุณ ควรละเว้นการเพิ่มประสิทธิภาพแบตเตอรี่ + ละเว้นการเพิ่มประสิทธิภาพ + ขาดการอนุญาต + Cloud Messaging คือผู้ให้บริการการแจ้งเตือนแบบพุชที่แอปพลิเคชันของบุคคลที่สามจำนวนมากใช้ หากต้องการใช้งาน คุณต้องเปิดใช้งานการลงทะเบียนอุปกรณ์ + รอบในการส่งข้อความบนคลาวด์ + เกี่ยวกับบริการของ microG + เกิดข้อผิดพลาดในการยกเลิกการลงทะเบียน + ไม่ติดตั้งอีกต่อไป + ไม่ได้ลงทะเบียน + ไม่ได้รับข้อความใดๆจนถึงขณะนี้ + ลงทะเบียนแล้ว + ลงทะเบียนตั้งแต่: %1$s + ยกเลิกการลงทะเบียน %1$s\? + คุณปฏิเสธไม่ให้แอปลงทะเบียนรับข้อความแจ้งเตือนแบบพุชที่ลงทะเบียนไปแล้ว\nคุณต้องการยกเลิกการลงทะเบียนทันทีเพื่อไม่ให้รับข้อความแจ้งเตือนแบบพุชในอนาคตหรือไม่? + อนุญาตให้แอปลงทะเบียนเพื่อรับการแจ้งเตือนแบบพุช + เริ่มการทำงานของแอปด้วยข้อความแบบพุช + เริ่มการทำงานของแอปในขณะที่อยู่ในพื้นหลังเพื่อรับข้อความพุชที่เข้ามา + แอปกำลังใช้การแจ้งเตือนแบบพุช + แอปที่ลงทะเบียนแล้ว + แอปที่ไม่ได้ลงทะเบียน + เครือข่ายที่จะใช้สำหรับการแจ้งเตือนแบบพุช + ล้มเหลว: %s + คำเตือน: %s + กำลังทำงาน… + โหมดการทำงาน + ล้างคำร้องขอล่าสุด + ใช้งานล่าสุด: %1$s + จริง + กำหนดเอง: %s + อัตโนมัติ: %s + ระบบ: %s + นำเข้าโปรไฟล์ที่กำหนดเอง + เลือกโปรไฟล์ + โปรไฟล์อุปกรณ์ + การใช้งานล่าสุด + การรับรอง: %s + ReCaptcha: %s + ReCaptcha Enterprise: %s + คัดลอกข้อมูล JSON JWS + คำแนะนำ + ประเภทการประเมิน + สถานะการตอบกลับ + สถานะการตอบกลับ + ร้องขอข้อมูล + Nonce (เลขฐานสิบหก) + CTS ล้มเหลว + การยืนยันล้มเหลว + ยังไม่เสร็จสมบูรณ์ + ไม่มีผลลัพธ์ + JSON ไม่ถูกต้อง + เปิด / อัตโนมัติ: %s + เปิด / แมนนวล: %s + %s วินาที + %s นาที + เปิดใบอนุญาต + ปิดใบอนุญาต + การอนุญาตสิทธิ์ใช้งาน Google Play + ตอบคำขอตรวจสอบใบอนุญาต + เพิ่มแอปฟรีลงในไลบรารีโดยอัตโนมัติ + ขณะนี้ไม่สามารถให้ข้อเสนอแนะได้ + ไม่สามารถสำรองข้อมูลได้ในขณะนี้ + การเรียกเก็บเงินของ Google Play + จัดการคำขอเรียกเก็บเงิน + เมื่อเปิดใช้งานแล้ว แอปบางตัวสามารถทำการซื้อหรือเริ่มการสมัครสมาชิกผ่านบริการ Play Billing ของ Google ได้ + แอปบางตัวอาจต้องการให้คุณเปิดใช้การตรวจสอบใบอนุญาตเพื่อยืนยันการซื้อของคุณด้วย + ยกเลิก + ดำเนินการต่อ + การลงชื่อเข้าใช้ของคุณ + ดำเนินการต่อเป็น %1$s + ลงชื่อเข้าใช้ %1$s อีกครั้งด้วย Google + ลงนามในฐานะ %1$s + คุณสามารถจัดการการลงชื่อเข้าใช้ด้วย Google ในบัญชี Google ของคุณได้ + เลือกบัญชี + เพื่อดำเนินการต่อ %1$s + ข้อมูลเวอร์ชันและไลบรารีที่ใช้ + ยกเลิกการลงทะเบียน + ข้อความล่าสุด: %1$s + แอปบางตัวจะไม่ลงทะเบียนใหม่โดยอัตโนมัติและ/หรือไม่มีตัวเลือกให้ดำเนินการดังกล่าวด้วยตนเอง แอปเหล่านี้อาจไม่ทำงานอย่างถูกต้องหลังจากยกเลิกการลงทะเบียน\nดำเนินการต่อหรือไม่? + Google SafetyNet เป็นระบบการรับรองอุปกรณ์ ซึ่งรับรองว่าอุปกรณ์ได้รับการรักษาความปลอดภัยอย่างเหมาะสมและเข้ากันได้กับ Android CTS แอปพลิเคชันบางตัวใช้ SafetyNet เพื่อเหตุผลด้านความปลอดภัยหรือเป็นข้อกำหนดเบื้องต้นสำหรับการป้องกันการงัดแงะ\n\nmicroG GmsCore มีการนำ SafetyNet มาใช้ได้ฟรี แต่เซิร์ฟเวอร์อย่างเป็นทางการต้องการให้มีการลงนามคำขอ SafetyNet โดยใช้ระบบ ที่เป็นกรรมสิทธิ์ ของDroidGuard + แอปฟรีสามารถตรวจสอบว่าดาวน์โหลดมาจาก Google Play หรือไม่ เพิ่มแอปฟรีลงในไลบรารีบัญชีของคุณโดยอัตโนมัติเพื่อให้ผ่านการตรวจสอบสำหรับแอปฟรีทั้งหมดที่มีให้คุณใช้งานในปัจจุบัน + แอปบางตัวต้องได้รับการยืนยันว่าคุณซื้อแอปเหล่านั้นจาก Google Play เมื่อแอปร้องขอ microG จะสามารถดาวน์โหลดหลักฐานการซื้อจาก Google ได้ หากปิดใช้งานหรือไม่ได้เพิ่มบัญชี Google คำขอตรวจสอบใบอนุญาตจะถูกละเว้น + คุณกำลังใช้บริการ microG Limited ซึ่งต่างจากบริการ microG ทั่วไป ฟังก์ชันนี้จะทำงานได้เฉพาะกับแอปที่ใช้ไลบรารี microG เท่านั้น ไม่ใช่กับแอปบน Google Play ซึ่งหมายความว่าแอปพลิเคชันส่วนใหญ่จะละเว้นบริการเหล่านี้ + ฟีเจอร์นี้อยู่ในขั้นทดลองและอาจนำไปสู่การสูญเสียเงิน คุณได้รับคำเตือนแล้ว + หากดำเนินการต่อ Google จะแชร์ชื่อ ที่อยู่อีเมล และรูปโปรไฟล์ของคุณกับ %1$s ดูนโยบายความเป็นส่วนตัวและข้อกำหนดในการให้บริการของ %1$s + ลงชื่อเข้าใช้ด้วย Google + รับการแจ้งเตือนแบบพุช + อนุญาตให้ลงทะเบียน + อนุญาตให้ %1$s ลงทะเบียนเพื่อรับการแจ้งเตือนแบบพุชหรือไม่? + ข้อความ: %1$d (%2$d ไบต์) + ถูกตัดการเชื่อมต่อ + เชื่อมต่อตั้งแต่ %1$s + อนุญาติให้รับรองอุปกรณ์ + ทดสอบ ReCAPTCHA + ทดสอบ ReCAPTCHA Enterprise + ผ่านการทดสอบทั้งหมด + ทดสอบการรับรอง SafetyNet + อุปกรณ์นี้ไม่รองรับการทำงานของ DroidGuard บริการ SafetyNet อาจทำงานไม่ถูกต้อง + แอปกำลังใช้งาน SafetyNet + เนทีฟ + ซีเรียล + นำเข้าโปรไฟล์อุปกรณ์จากไฟล์ + ประเภทคำร้องขอ + ข้อมูลพื้นฐาน + โทเค็น + ยืนยันเข้ากันได้และผ่าน CTS + คัดลอกไปยังคลิปบอร์ดแล้ว! + เวลาการร้องขอ + ปิด + ฉันเข้าใจ + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-tr/permissions.xml b/play-services-core/src/main/res/values-tr/permissions.xml index 0164053999..bc955e5588 100644 --- a/play-services-core/src/main/res/values-tr/permissions.xml +++ b/play-services-core/src/main/res/values-tr/permissions.xml @@ -78,4 +78,82 @@ Uygulamanın, ilgili herhangi bir Google hesabı aracılığıyla Kişiselleştirilmiş Konuşma Tanıma\'ya erişmesine izin verir. Uygulamanın, ilgili herhangi bir Google hesabı aracılığıyla YouTube\'a erişmesine izin verir. YouTube kullanıcı adları + AdSense barındırıcı verilerinizi ve ilişkili hesaplarınızı görüntüleyin ve yönetin + AdSense verilerinizi görüntüleyin + AdSense verilerinizi görüntüleyin ve yönetin + Google Analytics verilerinizi görüntüleyin + Google Analytics verilerinizi görüntüleyin ve yönetin + Google Play Android Geliştiricisine Erişim + Groups Migration API\'ına okuma ve yazma erişimi. + Bir Google Apps Grubunun ayarlarını görüntüleyin ve yönetin + Lisans Yöneticisi API\'sine okuma/yazma erişimi. + Satıcı yöneticileri ve kullanıcılar için, API\'nin korumalı alanında test yaparken okuma/yazma erişimi veya bir API işlemini doğrudan çağırırken okuma/yazma erişimi. + Blogger hesabınızı yönetin + Blogger hesabınızı görüntüleyin + Kitaplarınızı yönetin + Takvimlerinizi yönetin + Takvimlerinizi görüntüleyin + Google bulut yazdırma verilerinizi görüntüleyin ve yönetin + Google Compute Engine kaynaklarınızı görüntüleyin + Google Compute Engine kaynaklarınızı görüntüleyin ve yönetin + Google Koordinatör işlerinizi görüntüleyin + Google Haritalar Koordinatör işlerinizi görüntüleyin ve yönetin + Google Bulut Depolama\'daki verilerinizi ve izinlerinizi yönetin + Google Bulut Depolama\'daki verilerinizi yönetme + DoubleClick for Advertisers raporlarını görüntüleme ve yönetme + Application Data klasörüne erişme izni + Google Drive uygulamalarınızı görüntüleyin + Bu uygulamayla açtığınız veya oluşturduğunuz Google Drive dosyalarını görüntüleyin ve yönetin + Google Drive\'ınızdaki dosya ve dokümanlara ilişkin meta verileri görüntüleyin + Google Drive\'ınızdaki dosya ve belgeleri görüntüleyin + Google Drive\'ınızdaki dosya ve belgeleri görüntüleyin ve yönetin + Verilerinizi Google Bulut Depolama\'da görüntüleyin + Kullanıcıların bir uygulamanın kurulumunu onaylamasına izin vermek için kullanılan özel kapsam + App Engine yönetici erişimi. + Genel okuma/yazma OAuth yetkisinin yanı sıra, müşterinin verisini getirirken yalnızca okuma OAuth yetkisini kullan. + Google BigQuery\'deki verinizi görme + Admin Audit API sadece okuma izni + App State servisini kullanma yetkisi. + Freebase hesabınızı görüntüleme + Freebase\'e kendi hesabınız ile giriş yapma + Fusion Tables öğelerinizi görüntüleme + GAN verinizi görme + Chrome için Cloud Messaging + Glass zaman çizelgesi yetkisi + Taslak oluştur, oku, düzenle ve sil. Taslak ve mesaj gönder. + Tüm yazma/okuma işlemleri fakat derhal, çöp kutusuna atmadan kalıcı silme işlemleri hariç. + Kullanılabilir en iyi konumunuzu ve konum geçmişinizi yönetme + Şehir seviyesindeki konumunuzu ve konum geçmişinizi yönetme + Google Maps Engine verinizi görüntüleme ve yönetme + Google Maps Engine verinizi görüntüleme + Mobil deneyim için Google Haritalar\'ınızı görüntüleme ve yönetme + Orkut aktivitenizi yönetme + Orkut verinizi görme + Adınızı, temel bilginizi ve Google+\'daki bağlantılarınızı öğrenme + Google\'da kim olduğunuzu öğrenme + Google Prediction API verinizi yönetme + Ürün verinizi yönetme + Yetkilisi olduğunuz siteleri ve alan adlarını yönetme + Görev kuyruklarınızdaki görevlerinizi kullanma + Görevlerinizi yönetme + Görevlerinizi yönetme + goo.gl kısa bağlantılarınızı yönetme + E-posta adresinizi görüntüleme + Hesabınız hakkında temel bilgileri görüntüleme + YouTube hesabınızı yönetme + YouTube\'daki varlıklarınızı ve ilişkili içeriklerinizi görüntüleme ve yönetme + YouTube içeriğinizdeki YouTube Analytics parasal raporları görüntüleme + YouTube içeriğinizdeki YouTube Analytics raporları görüntüleme + Kullanılabilir en iyi konumunuzu yönetme + Şehir seviyesindeki konumunuzu yönetme + Fusion Tables öğelerinizi yönetme + GAN verinizi yönetme + Tüm kaynakları ve metaverilerini oku — yazma erişimi olmadan. + Google BigQuery\'deki verinizi görme veya yönetme + Google Apps Script komutlarının davranışını değiştirme + Google Play Oyunlar verinize erişme yetkisi. + Görevlerinizi görüntüleme + Shopping Content API\'a okuma/yazma erişimi. + Google Maps Tracks API, Bu yetki, projenizin verisine okuma ve yazma erişimi sağlar. + Google ile yeni site doğrulamalarınızı yönetme \ No newline at end of file diff --git a/play-services-core/src/main/res/values-tr/plurals.xml b/play-services-core/src/main/res/values-tr/plurals.xml index 6354030dfa..e5b8da147c 100644 --- a/play-services-core/src/main/res/values-tr/plurals.xml +++ b/play-services-core/src/main/res/values-tr/plurals.xml @@ -5,8 +5,8 @@ %1$d backend yapılandırıldı - <xliff:g example=“1”>%1$d</xliff:g> kayıtlı Uygulama - <xliff:g example=“123”>%1$d</xliff:g> kayıtlı Uygulama + %1$d kayıtlı uygulama + %1$d kayıtlı uygulama Eksik izin talebi diff --git a/play-services-core/src/main/res/values-tr/strings.xml b/play-services-core/src/main/res/values-tr/strings.xml index 2bfc89122a..ba0ee677e6 100644 --- a/play-services-core/src/main/res/values-tr/strings.xml +++ b/play-services-core/src/main/res/values-tr/strings.xml @@ -25,9 +25,7 @@ %1$s şunu istiyor: %1$s şunu kullanmak istiyor: Google Hesap Yöneticisi - Cihazınızdaki bir uygulama, Google hesabına giriş yapmaya çalışıyor. -\n -\nEğer bu istediğiniz bir şey ise, Oturum aç tuşunu kullanarak Google\'ın oturum açma sayfasına bağlanın, eğer değilse, İptal tuşuna basarak bu menünün çıkmasına neden olan uygulamaya geri dönün. + Cihazınızdaki bir uygulama, Google hesabına giriş yapmaya çalışıyor.\n\nEğer bu istenilen bir şey ise, Oturum aç tuşunu kullanarak Google\'ın oturum açma sayfasına giriş yapın, eğer değilse, İptal tuşuna basarak oturum açmak isteyen uygulamaya geri dönün. Oturum aç Hay aksi… Cihazınız, oturum açmanız için Google sunucuları ile iletişim kuruyor. @@ -244,4 +242,24 @@ Hesap seç Özgür uygulamaları otomatik olarak kütüphaneye ekle Özgür uygulamalar Google Play\'den indirilip indirilmediklerini denetleyebilir. Şu anda kullanabileceğiniz tüm özgür uygulamaların denetimini her zaman geçmek için özgür uygulamaları otomatik olarak hesap kütüphanenize ekleyin. + microG Kısıtlı Servisleri + Google hesap uyarıları + Hesaba eylem gerekiyor + Google hesabınızın ek kuruluma ihtiyacı var. + Google hesabınızı ayarlamayı bitirin + Google hesaplarınızdan biri kullanılmadan önce ek kurulum gerektirdiğinde veya bir hesap, microG ile uyumsuz olduğunda bildirimde bulunur. + Bu cihazda %s Google hesabınızı kullanmak için verilen adımları tamamlayın. + Cihazınızın, en azından bir seferliğine Google\'a kaydettirilmesi gerekiyor.\n\nHesap kurulumu tamamlandıktan sonra Google cihaz kaydını devre dışı bırakabilirsiniz. + Cloud Messaging\'i etkinleştir + Güvenli ekran kilidi ayarla + Hesap kurulumu tamamlandıktan sonra Cloud Messaging\'i geri kapatabilirsiniz. + microG için Cloud Messaging\'e izin ver + Tercihlerinize göre, microG\'nin kendisini Cloud Messaging\'e kaydetmesi için sizin yetki vermeniz gerekiyor. + Adım tamamlandı + Adımı gerçekleştirmek için dokunun + Bitir + Anlaşıldı + Cihaz kaydını etkinleştir + Google hesabınız, eğitim kurumunuz veya çalışma ortamınız tarafından yönetiliyor. Yöneticiniz, hesap verilerine erişilmeden önce cihazların bir ekran kilidine sahip olmasını zorunlu kıldı.\n\nBundan dolayı, lütfen cihazınızı bir şifre, PIN veya desen kilidi ile koruyun. + microG Kısıtlı Servisleri\'ni kullanıyorsunuz. Normal microG Servisleri\'ne kıyasla, microG\'nin bu varyasyonu, Google Play\'deki uygulamalarla değil, özellikle microG kütüphanelerini kullanan uygulamalarla çalışacaktır. Diğer bir deyişle, çoğu uygulama, bu servisleri görmezden gelecektir. \ No newline at end of file diff --git a/play-services-core/src/main/res/values-uk/strings.xml b/play-services-core/src/main/res/values-uk/strings.xml index 02c9f961ee..f39918ca1e 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -23,7 +23,7 @@ %1$s бажав би використовувати: Google Account Manager Вибачте… - Застосунок на вашому пристрої намагається увійти до облікового запису Google.. \u0020Якщо ви очікували на це, натисніть кнопку Увійти для під\'єднання до сторінки авторизації Google, інакше натисніть Скасувати, аби перейти назад до застосунку, що викликав це вікно. + Застосунок на вашому пристрої намагається увійти до облікового запису Google.\n\nЯкщо ви очікували на це, натисніть кнопку Увійти для під\'єднання до сторінки авторизації Google, інакше натисніть Скасувати, аби перейти назад до застосунку, що викликав це вікно. Увійти Ваш пристрій встановлює зв\'язок із серверами Google для авторизації. \n @@ -255,4 +255,27 @@ %1$s хоче отримати доступ до вашого облікового запису під видом %2$s by %3$s. Це може надати йому привілейований доступ до вашого облікового запису. Автоматично додавати безплатні застосунки до бібліотеки Безплатні застосунки можуть перевіряти, чи були вони завантажені з Google Play. Автоматично додавайте безплатні програми до бібліотеки свого облікового запису, щоб завжди проходити перевірку всіх доступних вам безоплатних застосунків. + Обмежені сервіси microG + Я розумію + Ви користуєтеся «Обмежені сервіси microG». На відміну від звичайних сервісів microG, цей варіант працює лише із застосунками, що використовують бібліотеки microG, а не з тими, що є у Google Play. Це означає, що більшість застосунків ігноруватимуть ці служби. + Увімкнути реєстрацію пристрою + Увімкнути хмарні повідомлення + Відповідно до ваших налаштувань, microG потребує вашого дозволу, перш ніж зареєструватися у хмарних повідомленнях. + Натисніть, щоби виконати крок + Крок виконано + Завершити + Налаштувати надійне блокування екрана + Ваш обліковий запис Google управляється на вашому робочому місці або у навчальному закладі. Ваш адміністратор вирішив, що пристрої потребують надійного блокування екрана, перш ніж отримати доступ до даних облікового запису.\n\nНалаштуйте пароль, PIN-код або графічний ключ для блокування екрана. + Сповіщення про обліковий запис Google + Сповіщає, коли один з ваших облікових записів Google потребує додаткового налаштування перед використанням або коли обліковий запис несумісний з microG. + Потрібні дії з обліковим записом + Ваш обліковий запис Google потребує додаткового налаштування. + Виконайте наведені нижче дії, щоби мати змогу використовувати свій обліковий запис Google %s на цьому пристрої. + Завершення налаштування облікового запису Google + Ваш пристрій повинен зареєструватися в Google принаймні один раз.\n\nВи можете вимкнути реєстрацію пристрою в Google після завершення налаштування облікового запису. + Дозволити хмарні повідомлення для microG + Ви можете вимкнути хмарні повідомлення після завершення налаштування облікового запису. + Завантажити додаткові ресурси за запитом застосунків, які використовують Play Asset Delivery + Google Play Asset Delivery + Увімкнути доставку ресурсів на вимогу \ No newline at end of file diff --git a/play-services-core/src/main/res/values-vi/permissions.xml b/play-services-core/src/main/res/values-vi/permissions.xml index 3d6d0ef8b3..15e438d9bb 100644 --- a/play-services-core/src/main/res/values-vi/permissions.xml +++ b/play-services-core/src/main/res/values-vi/permissions.xml @@ -1,24 +1,159 @@ Danh bạ - Cho phép ứng dụng truy cập Danh bạ thông qua bất kỳ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Danh bạ thông qua bất kỳ tài khoản Google nào được liên kết. Dịch vụ Android Cho phép ứng dụng truy cập các dịch vụ Android thông qua bất cứ tài khoản Google nào được liên kết. AdWords - Cho phép ứng dụng truy cập AdWords thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào AdWords thông qua bất cứ tài khoản Google nào được liên kết. Google App Engine Blogger - Cho phép ứng dụng truy cập Blogger thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Blogger thông qua bất cứ tài khoản Google nào được liên kết. Lịch Google - Cho phép ứng dụng truy cập Lịch Google thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Lịch Google thông qua bất cứ tài khoản Google nào được liên kết. Xem tài khoản YouTube của bạn Xem báo cáo phân tích YouTube cho nội dung YouTube của bạn Tất cả dịch vụ Gooogle Cho phép ứng dụng truy cập tất cả các dịch vụ của Google thông qua bất cứ tài khoản Google nào được liên kết. AdSense - Cho phép ứng dụng truy cập AdSense thông qua bất cứ tài khoản Google nào được liên kết. - Cho phép ứng dụng truy cập Google App Engine thông qua bất cứ tài khoản Google nào được liên kết. - Xem báo cáo tiền tệ YouTube Analytics cho nội dung YouTube của bạn + Cho phép ứng dụng truy cập vào AdSense thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google App Engine thông qua bất cứ tài khoản Google nào được liên kết. + Xem báo cáo tiền tệ của YouTube Analytics cho nội dung YouTube của bạn Quản lý video YouTube của bạn - Xem và quản lý nội dung của bạn và nội dung liên quan trên YouTube + Quản lý tài sản cùng nội dung liên quan của bạn trên YouTube + Dodgeball + Google Finance + Cho phép ứng dụng truy cập vào Dodgeball thông qua bất kỳ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Finance thông qua bất kỳ tài khoản Google nào được liên kết. + Google Base + Cho phép ứng dụng truy cập vào Google Base thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Voice thông qua bất cứ tài khoản Google nào được liên kết. + Google Groups + Google Voice + Cho phép ứng dụng truy cập vào Google Groups thông qua bất cứ tài khoản Google nào được liên kết. + Google Health + Cho phép ứng dụng truy cập vào Google Health thông qua bất cứ tài khoản Google nào được liên kết. + iGoogle + Cho phép ứng dụng truy cập vào iGoogle thông qua bất kỳ tài khoản Google nào được liên kết. + JotSpot + Cho phép ứng dụng truy cập vào JotSpot thông qua bất kỳ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Knol thông qua bất kỳ tài khoản Google nào được liên kết. + Album Web Picasa + Knol + Cho phép ứng dụng truy cập vào Picasa Web Albums thông qua bất cứ tài khoản Google nào được liên kết. + Google Maps + Cho phép ứng dụng truy cập vào Google Maps thông qua bất cứ tài khoản Google nào được liên kết. + Google Mail + Cho phép ứng dụng truy cập vào Google Mail thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google News thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Nootbook thông qua bất kỳ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Orkut thông qua bất kỳ tài khoản Google nào được liên kết. + Google Notebook + Orkut + Google Tin tức + Google Tìm kiếm sách + Cho phép ứng dụng truy cập vào Tài khoản QA của Google Checkout thông qua bất cứ tài khoản Google nào được liên kết. + Google Webmaster Tools + Cho phép ứng dụng truy cập vào Google Sách thông qua bất kỳ tài khoản Google nào được liên kết. + Tài khoản Google Checkout + Tài khoản QA của Google Checkout + Tài khoản Sandbox của Google Checkout + Cho phép ứng dụng truy cập vào Tài khoản Sandbox của Google Checkout thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Webmaster Tools thông qua bất cứ tài khoản Google nào được liên kết. + Voice Search + Cho phép ứng dụng truy cập vào Voice Search thông qua bất kỳ tài khoản Google nào được liên kết. + Personalized Speech Recognition + Cho phép ứng dụng truy cập vào Personalized Speech Recognition thông qua bất kỳ tài khoản Google nào được liên kết. + Google Talk + Cho phép ứng dụng truy cập vào Google Talk thông qua bất kỳ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Wi-Fi thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Spreadsheets thông qua bất cứ tài khoản Google nào được liên kết. + Cho phép ứng dụng truy cập vào Google Tài liệu thông qua bất cứ tài khoản Google nào được liên kết. + YouTube + Cho phép ứng dụng truy cập vào Youtube thông qua bất cứ tài khoản Google nào được liên kết. + Google Wi-Fi + Tên người dùng YouTube + Cho phép ứng dụng truy cập vào Tên người dùng Youtube thông qua bất kỳ tài khoản Google nào được liên kết. + Xem lịch sử hoạt động Google Apps của bạn + Quản lý cấu hình tài khoản của người mua Ad Exchange + Xem dữ liệu Ad Exchange của bạn + Quản lý dữ liệu máy chủ AdSense của bạn và các tài khoản liên quan + Quản lý dữ liệu Ad Exchange của bạn + Xem dữ liệu AdSense của bạn + Quản lý dữ liệu AdSense của bạn + Xem dữ liệu Google Analytics của bạn + Quản lý dữ liệu Google Analytics của bạn + Truy cập vào Google Play Android Developer + Phạm vi quyền quản trị của App Engine. + Quản lý cài đặt của Google Apps Group + Cấp quyền đọc/viết cho quản trị viên và người dùng đại lý khi thử nghiệm trong sandbox của API, hoặc khi gọi trực tiếp hoạt động API. + Cấp quyền đọc/viết cho License Manager API. + Cấp quyền đọc và viết cho Groups Migration API. + Ngoài phạm vi OAuth đọc/ghi tổng thể, hãy sử dụng phạm vi OAuth chỉ đọc khi truy xuất dữ liệu của khách hàng. + Cấp quyền truy cập chỉ đọc cho Admin Audit API + Phạm vi sử dụng dịch vụ App State. + Xem dữ liệu của bạn trong Google BigQuery + Quản lý dữ liệu của bạn trong Google BigQuery + Quản lý tài khoản Blogger của bạn + Xem tài khoản Blogger của bạn + Quản lý sách của bạn + Quản lý lịch của bạn + Xem lịch của bạn + Quản lý dữ liệu in đám mây của Google + Xem tài nguyên Google Compute Engine của bạn + Quản lý tài nguyên Google Compute Engine của bạn + Xem công việc Google Coordinate của bạn + Quản lý công việc Google Coordinate của bạn + Quản lý dữ liệu và quyền của bạn trong Google Cloud Storage + Xem dữ liệu của bạn trong Google Cloud Storage + Quản lý dữ liệu của bạn trong Google Cloud Storage + Quản lý báo cáo DoubleClick for Advertisers + Cho phép truy cập vào thư mục Dữ liệu ứng dụng + Xem ứng dụng Google Drive của bạn + Quản lý các tệp Google Drive mà bạn đã mở hoặc tạo bằng ứng dụng này + Phạm vi đặc biệt được sử dụng để cho phép người dùng chấp thuận cài đặt ứng dụng + Xem metadata cho các tệp và tài liệu trong Google Drive của bạn + Xem tệp và tài liệu trong Google Drive của bạn + Chỉnh sửa cách thức hoạt động Google Apps Script của bạn + Quản lý tệp và tài liệu trong Google Drive của bạn + xem tài khoản Freebase của bạn + Quản lý Fusion Tables của bạn + Đăng nhập vào Freebase + Xem Fusion Tables của bạn + Phạm vi truy cập dữ liệu từ Google Play Games. + Quản lý dữ liệu GAN của bạn + Xem dữ liệu GAN của bạn + Phạm vi dòng thời gian Glass + CloudMessaging cho Chrome + Tạo, đọc, cập nhật và xóa bản nháp. Gửi tin nhắn và bản nháp. + Tất cả các thao tác đọc/ghi ngoại trừ việc xóa vĩnh viễn các chuỗi và tin nhắn, bỏ qua Thùng rác. + Đọc tất cả tài nguyên và metadata của chúng—không thực hiện thao tác ghi. + Quản lý vị trí tốt nhất có sẵn và lịch sử vị trí của bạn + Quản lý vị trí thành phố và lịch sử vị trí của bạn + Quản lý vị trí tốt nhất của bạn + Quản lý vị trí thành phố của bạn + Quản lý dữ liệu Google Maps Engine của bạn + Xem dữ liệu Google Maps Engine của bạn + Quản lý trải nghiệm Google Maps dành cho thiết bị di động của bạn + Quản lý hoạt động Orkut của bạn + Xem dữ liệu Orkut của bạn + Biết tên, thông tin cơ bản và danh sách những người bạn kết nối trên Google+ + Biết bạn là ai trên Google + Quản lý dữ liệu của bạn trong Google Prediction API + Xem dữ liệu sản phẩm của bạn + Quản lý danh sách trang web và tên miền bạn kiểm soát + Quản lý xác minh trang web mới của bạn với Google + Cấp quyền đọc/ghi vào Shopping Content API. + Xử lý các tác vụ từ Taskqueues của bạn + Quản lý Tasks của bạn + Quản lý các tác vụ của bạn + Xem các tác vụ của bạn + Google Maps Tracks API, phạm vi này cho phép truy cập đọc và ghi vào dữ liệu của dự án bạn. + Quản lý các URL rút gọn goo.gl của bạn + Xem địa chỉ email của bạn + Xem thông tin cơ bản về tài khoản của bạn + Quản lý tài khoản Youtube của bạn + Google Spreadsheets + Google Tài liệu + Cho phép ứng dụng truy cập vào tài khoản của Google Checkout thông qua bất cứ tài khoản Google nào được liên kết. \ No newline at end of file diff --git a/play-services-core/src/main/res/values-vi/plurals.xml b/play-services-core/src/main/res/values-vi/plurals.xml index a6b3daec93..b4708892c4 100644 --- a/play-services-core/src/main/res/values-vi/plurals.xml +++ b/play-services-core/src/main/res/values-vi/plurals.xml @@ -1,2 +1,15 @@ - \ No newline at end of file + + + %1$d backend được cấu hình + + + %1$d Ứng dụng đã đăng ký + + + Hiện chưa cấp các quyền truy cập cần thiết để Dịch vụ microG hoạt động một cách bình thường. + + + Yêu cầu cấp quyền còn thiếu + + \ No newline at end of file diff --git a/play-services-core/src/main/res/values-vi/strings.xml b/play-services-core/src/main/res/values-vi/strings.xml index d0eff52f76..6f33e44c63 100644 --- a/play-services-core/src/main/res/values-vi/strings.xml +++ b/play-services-core/src/main/res/values-vi/strings.xml @@ -1,18 +1,18 @@ - microG Services + Dịch vụ microG Cài đặt microG Thiết lập các dịch vụ của microG. - Đã xảy ra sự cố giao tiếp với máy chủ Google. + Đã xảy ra sự cố khi kết nối với máy chủ Google. \n \nHãy thử lại sau. Thiết bị của bạn đang liên lạc với Google để lưu thông tin vào tài khoản của bạn. \n -\nViệc này có thể mất vài phút. +\nĐiều này có thể mất vài phút. Cho phép Từ chối Yêu cầu xác thực - 1%1$s yêu cầu xác thực từ bạn để truy cập tài khoản Google của bạn. + %1$s cần bạn ủy quyền để truy cập vào tài khoản Google của bạn. Chọn tài khoản Thêm tài khoản khác Cho phép và chia sẻ @@ -22,17 +22,242 @@ Đợi một chút… Bằng cách tiếp tục, bạn cho phép ứng dụng này và Google sử dụng thông tin của mình theo các điều khoản tương ứng của dịch vụ và chính sách bảo mật. Đăng nhập - Thiết bị của bạn đang thiết lập kết nối với máy chủ của Google để đăng nhập cho bạn. + Thiết bị của bạn đang thiết lập kết nối với máy chủ của Google để đăng nhập. \n -\nĐiều này cần khoảng vài giây để thực hiện. - Bạn hiện tại không có kết nối mạng. +\nĐiều này có thể mất vài giây. + Hiện bạn không có kết nối mạng. \n -\nĐây có thể là sự cố tạm thời hoặc thiết bị Android của bạn có thể không được cung cấp cho các dịch vụ dữ liệu. Thử lại khi kết nối với mạng di động hoặc kết nối với mạng Wi-Fi. - để tiếp tục 1%1$s - Cho phép bạn đăng nhập vào 1%1$s - Để tiếp tục, microG sẽ chia sẻ tên, địa chỉ email và ảnh hồ sơ từ tải khoản Google của bạn với 1%1$s. +\nĐây có thể là sự cố tạm thời hoặc thiết bị của bạn có thể chưa được cung cấp các dịch vụ dữ liệu. Hãy bật dữ liệu di động hoặc kết nối với Wi-Fi và thử lại. + để tiếp tục %1$s + Cho phép bạn đăng nhập vào %1$s + Để tiếp tục, microG sẽ chia sẻ tên, địa chỉ email và ảnh hồ sơ từ tải khoản Google của bạn với %1$s. Google - Quản lí tài khoản Google + Quản Lí Tài Khoản Google Xin lỗi… - Một ứng dụng trên thiết bị của bạn đang cố gắng đăng nhập vào tài khoản Google. \u0020Nếu điều này là do bạn thực hiện, hãy sử dụng nút Đăng nhập để kết nối với trang đăng nhập của Google, nếu không, nhấn Hủy để quay trở lại ứng dụng khiến hộp thoại này xuất hiện. + Một ứng dụng trên thiết bị của bạn đang cố gắng đăng nhập vào một tài khoản Google.\n\nNếu đây là hành động có mục đích, hãy nhấn vào nút Đăng nhập để kết nối đến trang đăng nhập của Google, nếu không, hãy nhấn Hủy để quay lại ứng dụng ban đầu. + Dịch vụ Hạn chế microG + Cho phép %1$s quyền truy cập đặc quyền vào %2$s\? + %1$s muốn truy cập vào tài khoản của bạn như thể nó là %2$s của %3$s. Điều này có thể cấp quyền truy cập đặc quyền vào tài khoản của bạn. + %1$s muốn: + %1$s muốn sử dụng: + Huỷ + Đăng nhập bằng Google + để tiếp tục đến %1$s + trao đổi và nhận thông báo đồng bộ từ máy chủ của Google + Mở rộng quyền truy cập vào các dịch vụ của Google + cung cấp dịch vụ microG + Cho phép ứng dụng cấu hình các dịch vụ microG mà không cần tương tác của người dùng + Tốc độ xe + Truy cập kênh nhà cung cấp xe của bạn để trao đổi thông tin cụ thể về xe + Đăng ký thiết bị Google + Google SafetyNet + Để sử dụng Play Games, bạn cần cài đặt ứng dụng Google Play Games. Ứng dụng có thể tiếp tục mà không cần Play Games, nhưng có khả năng ứng dụng sẽ hoạt động không như mong đợi. + Dữ liệu di dộng + Cloud Messaging + Hỗ trợ giả mạo chữ ký + Hệ thống + Hệ thống giả mạo chữ ký: + Thiếu quyền + Tùy chọn tài khoản + Thông tin cá nhân & quyền riêng tư + Khi bật, tất cả ứng dụng trên thiết bị này sẽ có thể xem địa chỉ email của Tài khoản Google của bạn mà không cần sự uỷ quyền trước. + Loại bỏ tên thiết bị để xác thực + Khi bật, các yêu cầu xác thực sẽ không bao gồm tên thiết bị, điều này có thể cho phép các thiết bị không được ủy quyền mà vẫn đăng nhập, nhưng có thể gây ra hậu quả không lường trước được. + Đăng ký thiết bị của bạn với các dịch vụ của Google và tạo một mã định danh thiết bị duy nhất. microG loại bỏ các bit nhận dạng khác ngoài tên tài khoản Google của bạn từ dữ liệu đăng ký. + ID Android + Chưa được đăng ký + Đăng ký lần cuối: %1$s + Đăng ký thiết bị + Trạng thái + Thêm + Tài khoản Google + Thêm và quản lý tài khoản Google + Cài đặt + Tài khoản + Tài khoản + Thêm tài khoản Google + Danh sách các ứng dụng hiện đang được đăng ký sử dụng Cloud Messaging. + Hỏi trước khi đăng ký một ứng dụng mới để nhận thông báo đẩy + Giới thiệu về Dịch vụ microG + Thông tin phiên bản và thư viện đã sử dụng + Lỗi khi hủy đăng ký + Đã đăng ký từ: %1$s + Hủy đăng ký %1$s\? + Một số ứng dụng không tự động đăng ký lại và/hoặc không cung cấp tùy chọn để thực hiện thủ công. Những ứng dụng này có thể không hoạt động chính xác sau khi hủy đăng ký.\nBạn có muốn tiếp tục không? + Thông báo: %1$d (%2$d byte) + Nhận thông báo đẩy + Cho phép %1$s đăng ký nhận thông báo đẩy? + Cho phép đăng ký + Ứng dụng sử dụng thông báo đẩy + Đang chạy… + Chứng thực: %s + ReCaptcha: %s + ReCaptcha Enterprise: %s + Sao chép dữ liệu JSON JWS + Lời khuyên + Nonce (Hex) + Thời gian yêu cầu + Loại yêu cầu + Token + Đã sao chép vào bảng nhớ tạm! + Đã vượt qua Integrity và kiểm tra CTS + Tự động thêm ứng dụng miễn phí vào thư viện + Ứng dụng miễn phí có thể kiểm tra xem chúng đã được tải xuống từ Google Play hay chưa. Tự động thêm ứng dụng miễn phí vào thư viện tài khoản của bạn để luôn vượt qua kiểm tra đối với tất cả các ứng dụng miễn phí hiện có sẵn cho bạn. + Tiếp tục + Đang đăng nhập cho bạn + Đăng nhập lại vào %1$s bằng Google + Đang đăng nhập với tư cách %1$s + Bằng cách tiếp tục, Google sẽ chia sẻ tên, địa chỉ email và ảnh hồ sơ của bạn với %1$s. Xem Chính sách quyền riêng tư và Điều khoản dịch vụ của %1$s. + Bạn đang sử dụng Dịch vụ microG Limited. Không giống như các Dịch vụ microG thông thường, phiên bản này chỉ hoạt động với các ứng dụng sử dụng thư viện microG, không phải các ứng dụng trên Google Play. Điều này có nghĩa là hầu hết các ứng dụng sẽ bỏ qua các dịch vụ này. + Nghe các thông báo trạng thái nội bộ + đọc cấu hình dịch vụ Google + nghe các thông báo C2DM + gửi các thông báo C2DM đến các ứng dụng khác + Mức nhiên liệu của xe + Số km xe đã đi + Truy cập tốc độ xe của bạn + Thông tin xe + Truy cập thông tin về số km đã đi của xe bạn + Truy cập thông tin xe của bạn + Truy cập thông tin mức nhiên liệu của xe bạn + Kênh nhà cung cấp xe + %1$s muốn sử dụng Play Games + Dịch vụ Play Store + Google Play Games + Chọn địa điểm + Trình chọn địa điểm hiện chưa có sẵn. + Chọn địa điểm này + Địa điểm gần đây + Dịch vụ microG: Không có quyền %1$s + (%1$.7f, %2$.7f) + Chuyển vùng quốc tế + Các mạng khác + Wi-Fi + Hệ thống cấp quyền giả mạo chữ ký: + Hệ thống có hỗ trợ giả mạo chữ ký: + Đây là một chỉ báo mạnh cho thấy ROM hỗ trợ giả mạo chữ ký, nhưng cần thêm thao tác để kích hoạt nó. Vui lòng kiểm tra tài liệu để biết về các bước cần thiết. + %1$s đã cài đặt: + Cửa hàng Play (Phonesky) + Khung dịch vụ (GSF) + Tắt tối ưu hóa pin: + Giới thiệu + Chạm vào đây để tắt tối ưu hóa pin. Không làm như vậy có thể khiến các ứng dụng hoạt động không bình thường. + Thành phần + Đã bật tính năng tối ưu hóa pin + Bạn đã bật Cloud Messaging nhưng cũng bật tối ưu hóa pin cho Dịch vụ microG. Để thông báo đẩy, bạn nên tắt tối ưu hóa pin. + Đăng nhập & bảo mật + Cho phép ứng dụng tìm tài khoản + Tin tưởng Google về quyền truy cập ứng dụng + Khi tắt, người dùng sẽ được hỏi trước khi yêu cầu uỷ quyền của ứng dụng được gửi đến Google. Một số ứng dụng sẽ không sử dụng được tài khoản Google nếu tắt tính năng này. + Xác thực bằng cách đăng ký thiết bị + Khi tắt, các yêu cầu xác thực sẽ không được liên kết với việc đăng ký thiết bị, điều này có thể cho phép các thiết bị không được uỷ quyền mà vẫn đăng nhập, nhưng có thể gây ra hậu quả không lường trước được. + Khoảng thời gian kiểm tra kết nối của Cloud Messaging + Cloud Messaging là nhà cung cấp dịch vụ thông báo đẩy được nhiều ứng dụng của bên thứ ba sử dụng. Để sử dụng, bạn phải bật đăng ký thiết bị. + Các ứng dụng sử dụng Cloud Messaging + Xác nhận ứng dụng mới + Không còn được cài đặt + Đã đăng ký + Chưa nhận được thông báo nào cho đến nay + Hủy đăng ký + Chưa đăng ký + Thông báo cuối cùng: %1$s + Đã ngắt kết nối + Đã kết nối từ %1$s + Khởi chạy ứng dụng khi ở chế độ nền để nhận các tin nhắn đẩy. + Cho phép xác thực thiết bị + Kiểm tra chứng nhận SafetyNet + Kiểm tra ReCAPTCHA + Đã vượt qua tất cả các bài kiểm tra + Thất bại: %s + Cảnh báo: %s + Thực thi DroidGuard không được hỗ trợ trên thiết bị này. Các dịch vụ SafetyNet có thể hoạt động không bình thường. + Ứng dụng sử dụng SafetyNet + Xóa các yêu cầu gần đây + Số sê-ri + Nhập hồ sơ thiết bị từ tệp + Chọn hồ sơ + Hồ sơ thiết bị + Sử dụng gần đây + Trạng thái phản hồi + Dữ liệu phản hồi + Yêu cầu dữ liệu + Một số ứng dụng yêu cầu xác thực rằng bạn đã mua chúng trên Google Play. Khi được ứng dụng yêu cầu, microG có thể tải xuống bằng chứng mua hàng từ Google. Nếu bị vô hiệu hóa hoặc không có tài khoản Google được thêm vào, các yêu cầu xác minh giấy phép sẽ bị bỏ qua. + Hiện tại không thể phản hồi + Hiện tại không thể sao lưu + Thanh toán Google Play + Cảnh báo: Tính năng này đang trong giai đoạn thử nghiệm và có thể dẫn đến mất tiền. + %1$s có chữ ký chính xác: + ROM của bạn không có hỗ trợ gốc cho việc giả mạo chữ ký. Bạn vẫn có thể sử dụng Xposed hoặc các hệ thống khác để giả mạo chữ ký. Vui lòng kiểm tra tài liệu về ROM nào hỗ trợ giả mạo chữ ký và cách sử dụng microG trên ROM không được hỗ trợ. + Hoặc là %1$s đã cài đặt không tương thích hoặc tính năng giả mạo chữ ký không hoạt động đối với nó. Vui lòng kiểm tra tài liệu về các ứng dụng và ROM nào tương thích. + Cấu hình + Dịch vụ của Google + Dịch vụ định vị + Dịch vụ + Mạng sử dụng cho thông báo đẩy + Chế độ hoạt động + Lần sử dụng cuối cùng: %1$s + Ứng dụng đã đăng ký + Gốc + Tùy chỉnh: %s + Tự động: %s + Thực + Loại đánh giá + Hệ thống: %s + Nhập hồ sơ tùy chỉnh + Chưa hoàn thành + Không có kết quả + BẬT/Thủ công: %s + %s giây + %s phút + Tắt cấp phép + Bật cấp phép + Cấp phép Google Play + Phản hồi các yêu cầu xác thực giấy phép + Xử lý yêu cầu thanh toán + Sau khi được bật, một số ứng dụng có thể hoàn tất giao dịch mua hoặc bắt đầu đăng ký thông qua dịch vụ thanh toàn Google Play. + Một số ứng dụng có thể yêu cầu bạn cũng phải bật xác thực giấy phép để xác minh các giao dịch mua của bạn. + Bạn có thể quản lý Đăng nhập bằng Google trong tài khoản Google của bạn. + Kiểm tra CTS không thành công + Các gói đã cài đặt + Vui lòng kiểm tra tài liệu để biết những bước nào có thể được yêu cầu. + Cài đặt ứng dụng %1$s hoặc ứng dụng tương thích. Vui lòng kiểm tra tài liệu về ứng dụng nào tương thích. + Dịch vụ Play (GmsCore) + Kiểm tra + Tắt tối ưu hóa pin + Khoảng thời gian tính bằng giây để hệ thống gửi tín hiệu đến máy chủ Google. Tăng giá trị này sẽ giảm tiêu thụ pin, nhưng có thể gây ra sự chậm trễ trong việc nhận thông báo đẩy.\nTính năng này đã bị loại bỏ và sẽ được thay thế trong các phiên bản sau. + Khoảng thời gian ping: %1$s + Bạn đã từ chối một ứng dụng đăng ký nhận thông báo đẩy đã được đăng ký.\nBạn có muốn hủy đăng ký ngay bây giờ để ứng dụng không nhận được tin nhắn đẩy trong tương lai không? + Cho phép ứng dụng đăng ký nhận thông báo đẩy. + Khởi chạy ứng dụng khi có thông báo đẩy + Ứng dụng chưa đăng ký + Google SafetyNet là hệ thống chứng nhận thiết bị, đảm bảo rằng thiết bị được bảo mật đúng cách và tương thích với Android CTS. Một số ứng dụng sử dụng SafetyNet vì lý do bảo mật hoặc như một điều kiện tiên quyết để bảo vệ chống giả mạo.\n\nmicroG GmsCore chứa một triển khai miễn phí của SafetyNet, nhưng máy chủ chính thức yêu cầu các yêu cầu SafetyNet phải được ký bằng hệ thống DroidGuard độc quyền. + Kiểm tra ReCAPTCHA Enterprise + Dữ liệu cơ bản + Integrity không thành công + JSON không hợp lệ + TẮT + BẬT/Tự động: %s + Tiếp tục như %1$s + Chọn tài khoản + Tôi hiểu + Hoàn thành các bước sau để có thể sử dụng tài khoản Google %s trên thiết bị này. + Tài khoản Google của bạn được quản lý bởi nơi làm việc hoặc cơ sở giáo dục của bạn. Quản trị viên của bạn đã quyết định rằng các thiết bị cần có màn hình khóa bảo mật trước khi chúng có thể truy cập dữ liệu tài khoản.\n\nVui lòng thiết lập màn hình khóa bằng mật khẩu, mã PIN hoặc hình vẽ. + Cảnh báo tài khoản Google + Hoàn tất thiết lập tài khoản Google của bạn + Thông báo khi một trong các tài khoản Google của bạn cần thiết lập thêm trước khi có thể sử dụng hoặc khi một tài khoản không tương thích với microG. + Tài khoản Google của bạn cần được thiết lập thêm. + Hành động tài khoản bắt buộc + Cho phép Cloud Messaging cho microG + Theo tuỳ chọn của bạn, microG cần có sự cho phép của bạn trước khi có thể tự đăng ký dịch vụ Cloud Messaging. + Kích hoạt Cloud Messaging + Bạn có thể tắt Cloud Messaging sau khi hoàn tất quá trình thiết lập tài khoản. + Kích hoạt đăng kí thiết bị + Thiết bị của bạn cần phải đăng ký với Google ít nhất một lần.\n\nBạn có thể tắt đăng ký thiết bị Google sau khi thiết lập tài khoản hoàn tất. + Cấu hình khóa màn hình bảo mật + Hoàn tất + Nhấn để thực hiện + Bước đã hoàn thành + Phân phối tài sản của Google Play + Cho phép phân phối tài sản theo yêu cầu + Tải xuống các tài sản bổ sung khi được các ứng dụng sử dụng Play Asset Delivery yêu cầu \ No newline at end of file diff --git a/play-services-core/src/main/res/values-zh-rCN/strings.xml b/play-services-core/src/main/res/values-zh-rCN/strings.xml index d56fec06fb..c9607f5c37 100644 --- a/play-services-core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-core/src/main/res/values-zh-rCN/strings.xml @@ -241,4 +241,27 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要 <b><xliff:g example=\"F-Droid\">%1$s</xliff:g></b> 想以 <xliff:g example=\"F-Droid Inc.\">%3$s</xliff:g></b> 的 <b><xliff:g example=\"F-Droid\">%2$s</xliff:g> 访问你的账户。这可能授予访问你账户的权限。 自动添加免费应用到库中 免费应用可能会检查它们是否曾经从 Google Play 被下载过。自动添加免费应用到你的账户库中可以让你一直通过当前对你可用的所有免费应用的检查。 + microG 有限服务 + 我理解 + 你在使用 microG 有限服务版本。和通常的 microG 服务不同,此版本仅适用使用 microG 库的应用,而非在 Google Play 上的应用。 这意味着多数应用会忽略这些服务。 + Google 账户警告 + 需要账户操作 + 启用设备注册 + 你的设备需要至少向 Google 注册一次。\n\n在账户设置完成后,你可以停用 Google 设备注册。 + 启用云消息传递 + 账户设置完成后,你可以停用云消息传递。 + 允许 microG 的云消息传递 + 根据你的首选项,microG 需要你的许可才能注册云消息传递。 + 配置安全屏幕锁 + 单击执行设置 + 步骤已完成 + 完成 + 在你多个 Google 账户中的一个需要额外设置才能使用或者一个账户不兼容 microG 时进行通知。 + 完成 Google 账户设置 + 你的 Google 账户需要额外设置。 + 要能在这台设备上使用你的 Google 账户 %s 请完成下列步骤。 + 你的 Google 账户受工作场所或教育机构管理。你的管理员决定设备在可以访问账户数据前需要设置安全屏幕锁。\n\n请设置一个密码、PIN或手势屏幕锁。 + 当使用 Play 资产传递的应用请求时下载额外的资产 + Google Play 资产传递 + 启用按需资产传递 \ No newline at end of file diff --git a/play-services-core/src/main/res/values/permissions.xml b/play-services-core/src/main/res/values/permissions.xml index 5c342b0082..dfc56c0729 100644 --- a/play-services-core/src/main/res/values/permissions.xml +++ b/play-services-core/src/main/res/values/permissions.xml @@ -90,6 +90,9 @@ YouTube usernames Allows app to access YouTube username(s) used with any associated Google account. + Activity recognition + Allows an app to receive periodic updates of your activity level from Google, for example, if you are walking, driving, cycling, or stationary. + View the activity history of your Google Apps Manage your Ad Exchange buyer account configuration View your Ad Exchange data diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 062aa8d1e7..6fd512c5eb 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -294,6 +294,10 @@ Please set up a password, PIN, or pattern lock screen." This feature is experimental and may lead to loss of money. You have been warned. Some apps may require you to also enable license verification to verify your purchases. + Google Play Asset Delivery + Enable on-demand asset delivery + Download additional assets when requested by apps that use Play Asset Delivery + Cancel Continue Signing you in diff --git a/play-services-core/src/main/res/xml/preferences_vending.xml b/play-services-core/src/main/res/xml/preferences_vending.xml index 3cc42b4fa9..509e994b83 100644 --- a/play-services-core/src/main/res/xml/preferences_vending.xml +++ b/play-services-core/src/main/res/xml/preferences_vending.xml @@ -51,4 +51,15 @@ android:summary="@string/pref_vending_billing_note_licensing" app:iconSpaceReserved="false" /> + + + + diff --git a/play-services-droidguard/core/src/main/res/values-de/strings.xml b/play-services-droidguard/core/src/main/res/values-de/strings.xml index 27f1230162..8d02a63d6c 100644 --- a/play-services-droidguard/core/src/main/res/values-de/strings.xml +++ b/play-services-droidguard/core/src/main/res/values-de/strings.xml @@ -4,4 +4,5 @@ Eingebettet Lokale DroidGuard-Laufzeit verwenden Über das Netzwerk mit der DroidGuard-Laufzeit verbinden + Externe \ No newline at end of file diff --git a/play-services-droidguard/core/src/main/res/values-fr/strings.xml b/play-services-droidguard/core/src/main/res/values-fr/strings.xml index a6b3daec93..d972aba4f6 100644 --- a/play-services-droidguard/core/src/main/res/values-fr/strings.xml +++ b/play-services-droidguard/core/src/main/res/values-fr/strings.xml @@ -1,2 +1,8 @@ - \ No newline at end of file + + Mode d\'opération DroidGuard + Embarqué + A distance + Exécuter localement DroidGuard + Exécuter DroidGuard via une connexion réseau + \ No newline at end of file diff --git a/play-services-droidguard/core/src/main/res/values-ja/strings.xml b/play-services-droidguard/core/src/main/res/values-ja/strings.xml index a6b3daec93..9845c8e278 100644 --- a/play-services-droidguard/core/src/main/res/values-ja/strings.xml +++ b/play-services-droidguard/core/src/main/res/values-ja/strings.xml @@ -1,2 +1,8 @@ - \ No newline at end of file + + DroidGuard操作モード + 組み込み型 + ローカルの DroidGuard ランタイムを使用する + リモート + ネットワーク経由でDroidGuardランタイムに接続する + \ No newline at end of file diff --git a/play-services-droidguard/core/src/main/res/values-th/strings.xml b/play-services-droidguard/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..b5edb200bd --- /dev/null +++ b/play-services-droidguard/core/src/main/res/values-th/strings.xml @@ -0,0 +1,8 @@ + + + ฝังตัว + ระยะไกล + เชื่อมต่อกับรันไทม์ DroidGuard ผ่านเครือข่าย + โหมดการทำงานของ DroidGuard + ใช้รันไทม์ DroidGuard ในเครื่อง + \ No newline at end of file diff --git a/play-services-droidguard/core/src/main/res/values-tr/strings.xml b/play-services-droidguard/core/src/main/res/values-tr/strings.xml index c051d60522..fe14e104a5 100644 --- a/play-services-droidguard/core/src/main/res/values-tr/strings.xml +++ b/play-services-droidguard/core/src/main/res/values-tr/strings.xml @@ -2,7 +2,7 @@ DroidGuard çalışma modu Gömülü - Yerel DroidGuard çalışma zamanını kullan + Yerel DroidGuard çalıştırıcısını kullan Uzak - Ağ üzerinden DroidGuard çalışma zamanına bağlan + Ağ üzerinden DroidGuard çalıştırıcısına bağlan \ No newline at end of file diff --git a/play-services-droidguard/core/src/main/res/values-vi/strings.xml b/play-services-droidguard/core/src/main/res/values-vi/strings.xml index a6b3daec93..f899a28342 100644 --- a/play-services-droidguard/core/src/main/res/values-vi/strings.xml +++ b/play-services-droidguard/core/src/main/res/values-vi/strings.xml @@ -1,2 +1,8 @@ - \ No newline at end of file + + Chế độ hoạt động của DroidGuard + Nhúng + Sử dụng runtime của DroidGuard cục bộ + Từ xa + Kết nối đến runtime của DroidGuard qua mạng + \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-fr/strings.xml b/play-services-fido/core/src/main/res/values-fr/strings.xml index a6b3daec93..a4dae50f8e 100644 --- a/play-services-fido/core/src/main/res/values-fr/strings.xml +++ b/play-services-fido/core/src/main/res/values-fr/strings.xml @@ -1,2 +1,27 @@ - \ No newline at end of file + + Utilisez votre clé de sécurité avec %1$s + Merci de saisir le code PIN de votre authentifiant + 4 à 63 caractères + OK + Annuler + Mauvais code PIN saisi ! + Utiliser votre clé de sécurité avec %1$s contribue à protéger vos données privées. + Commencer + %1$s agit en tant que navigateur de confiance pour utiliser votre clé de sécurité avec %2$s. + Oui, %1$s est mon navigateur de confiance et peut être autorisé à utiliser des clés de sécurité avec des sites web tiers. + Choisir comment utiliser votre clé de sécurité + Les clés de sécurité fonctionnent via Bluetooth, NFC ou USB. Choisissez comment utiliser votre clé. + Confirmez votre identité + %1$s nécessite de vérifier que c\'est bien vous. + Connecter votre clé de sécurité USB + Connectez votre clé de sécurité à un port USB ou connectez-là avec un câble USB. Si votre clé a un bouton ou un disque doré, appuyez dessus. + Connecter votre clé de sécurité NFC + Tenez votre clé contre l\'arrière de votre appareil jusqu\'à la fin des vibrations + Utiliser votre clé de sécurité via Bluetooth + Utiliser votre clé de sécurité via NFC + Utiliser votre clé de sécurité via USB + Utiliser le verrouillage écran de cet appareil + Merci de connecter votre clé de sécurité USB. + Merci d\'appuyer sur l\'anneau ou disque doré sur %1$s. + \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-it/strings.xml b/play-services-fido/core/src/main/res/values-it/strings.xml index 2830bf9e92..440f54845b 100644 --- a/play-services-fido/core/src/main/res/values-it/strings.xml +++ b/play-services-fido/core/src/main/res/values-it/strings.xml @@ -19,4 +19,9 @@ Usa chiave di sicurezza via Bluetooth Le chiavi di sicurezza funzionano via Bluetooth, NFC e USB. Scegli come vuoi usare la tua chiave. Usa chiave di sicurezza con l\'NFC + Inserisci il PIN per l\'autenticazione + Da 4 a 63 caratteri + OK + Annulla + PIN inserito errato! \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-ja/strings.xml b/play-services-fido/core/src/main/res/values-ja/strings.xml index a6b3daec93..d7ba6ecff5 100644 --- a/play-services-fido/core/src/main/res/values-ja/strings.xml +++ b/play-services-fido/core/src/main/res/values-ja/strings.xml @@ -1,2 +1,21 @@ - \ No newline at end of file + + セキュリティキーは、Bluetooth、NFC、USBで動作します。キーの使用方法を選択してください。 + 振動が止まるまで、キーをデバイスの背面に平らにかざします + NFCでセキュリティキーを使用する + USBでセキュリティキーを使用する + 4から63文字 + %1$sでセキュリティキーを使用する + %1$sでセキュリティキーを使用すると、プライベートデータを保護できます。 + 始める + %1$sは、%2$sでセキュリティキーを使用する信頼できるブラウザとして機能します。 + はい、%1$sは私の信頼できるブラウザであり、サードパーティのWebサイトでセキュリティキーを使用することが許可されるべきです。 + セキュリティキーの使用方法を選択してください + %1$sはあなたであることを確認する必要があります。 + USBセキュリティキーを接続する + セキュリティキーをUSBポートに接続するか、USBケーブルで接続します。キーにボタンやゴールドディスクがある場合は、今すぐタップしてください。 + NFCセキュリティキーを接続する + Bluetoothでセキュリティキーを使用する + このデバイスでスクリーンロックを使用してください + USBセキュリティキーを接続してください。 + \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-sr/strings.xml b/play-services-fido/core/src/main/res/values-sr/strings.xml index 78a903790f..1025c22359 100644 --- a/play-services-fido/core/src/main/res/values-sr/strings.xml +++ b/play-services-fido/core/src/main/res/values-sr/strings.xml @@ -19,9 +19,9 @@ Користите безбедносни кључ са Bluetooth-ом Безбедносни кључеви раде са Bluetooth-ом, NFC-ом и USB-ом. Изаберите како желите да користите свој кључ. Користите безбедносни кључ са NFC-ом - Унесите ПИН за ваш аутентификатор + Унесите PIN за ваш аутентификатор од 4 до 63 знакова ОК Откажи - Унет је погрешан ПИН! + Унет је погрешан PIN! \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-sv/strings.xml b/play-services-fido/core/src/main/res/values-sv/strings.xml index 146a42dad7..244e1f409f 100644 --- a/play-services-fido/core/src/main/res/values-sv/strings.xml +++ b/play-services-fido/core/src/main/res/values-sv/strings.xml @@ -19,4 +19,9 @@ Använd säkerhetsnyckel med Bluetooth Säkerhetsnycklar fungerar med Bluetooth, NFC och USB. Välj hur du vill använda nyckeln. Använd säkerhetsnyckel med NFC + 4 till 63 tecken + Vänligen ange PIN-kod för din autentisering + OK + Avbryt + Fel PIN-kod! \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-th/strings.xml b/play-services-fido/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..c1463b08e9 --- /dev/null +++ b/play-services-fido/core/src/main/res/values-th/strings.xml @@ -0,0 +1,27 @@ + + + การใช้คีย์ความปลอดภัยของคุณกับ %1$s จะช่วยปกป้องข้อมูลส่วนตัวของคุณ + เริ่มต้นใช้งาน + เลือกวิธีใช้คีย์ความปลอดภัยของคุณ + คีย์ความปลอดภัยทำงานร่วมกับ Bluetooth, NFC และ USB เลือกวิธีที่คุณต้องการใช้คีย์ของคุณ + ยืนยันตัวตนของคุณ + %1$s จำเป็นต้องยืนยันว่าเป็นคุณ + เชื่อมต่อคีย์ความปลอดภัย NFC ของคุณ + ใช้คีย์ความปลอดภัยร่วมกับ Bluetooth + ใช้คีย์ความปลอดภัยกับ NFC + ใช้คีย์ความปลอดภัยกับ USB + โปรดเชื่อมต่อคีย์ความปลอดภัย USB ของคุณ + กรุณาแตะที่วงกลมสีทองหรือที่วงกลม %1$s. + กรุณากรอกรหัส PIN สำหรับผู้ตรวจสอบสิทธิ์ของคุณ + ตกลง + ป้อน PIN ผิด! + ใช้คีย์ความปลอดภัยของคุณกับ %1$s + %1$s ทำหน้าที่เป็นเบราว์เซอร์ที่เชื่อถือได้เพื่อใช้รหัสความปลอดภัยของคุณกับ %2$s + ใช่ %1$s เป็นเบราว์เซอร์ที่เชื่อถือได้ของฉัน และควรได้รับอนุญาตให้ใช้คีย์ความปลอดภัยกับเว็บไซต์บุคคลที่สาม + เชื่อมต่อคีย์ความปลอดภัย USB ของคุณ + เชื่อมต่อกุญแจความปลอดภัยของคุณกับพอร์ต USB หรือเชื่อมต่อด้วยสาย USB หากกุญแจของคุณมีปุ่มหรือวงกลมสีทอง ให้แตะทันที + ถือกุญแจของคุณให้แนบกับด้านหลังของอุปกรณ์จนกว่าจะหยุดสั่น + ใช้เครื่องนี้ด้วยการล็อคหน้าจอ + 4 ถึง 63 ตัวอักษร + ยกเลิก + \ No newline at end of file diff --git a/play-services-fido/core/src/main/res/values-vi/strings.xml b/play-services-fido/core/src/main/res/values-vi/strings.xml index a6b3daec93..c1b3ca4a85 100644 --- a/play-services-fido/core/src/main/res/values-vi/strings.xml +++ b/play-services-fido/core/src/main/res/values-vi/strings.xml @@ -1,2 +1,27 @@ - \ No newline at end of file + + Sử dụng khóa bảo mật của bạn với %1$s + Sử dụng khóa bảo mật với %1$s giúp bảo vệ dữ liệu riêng tư của bạn. + Bắt đầu + %1$s hoạt động như một trình duyệt đáng tin cậy để sử dụng khóa bảo mật của bạn với %2$s. + Vâng, %1$s là trình duyệt đáng tin cậy của tôi và được phép sử dụng khóa bảo mật với các trang web của bên thứ ba. + Chọn cách sử dụng khóa bảo mật của bạn + Khóa bảo mật hoạt động với Bluetooth, NFC và USB. Chọn cách bạn muốn sử dụng khóa của mình. + Xác minh danh tính của bạn + %1$s cần xác minh đó là bạn. + Kết nối khóa bảo mật USB của bạn + Kết nối khóa bảo mật NFC của bạn + Giữ khóa của bạn nằm phẳng trên mặt sau của thiết bị cho đến khi nó ngừng rung + Sử dụng khóa bảo mật với Bluetooth + Sử dụng khóa bảo mật với NFC + Sử dụng khóa bảo mật với USB + Sử dụng thiết bị này với khóa màn hình + Vui lòng kết nối khóa bảo mật USB của bạn. + Vui lòng nhấn vào vòng tròn hoặc đĩa vàng trên %1$s. + Vui lòng nhập mã PIN cho thiết bị xác thực của bạn + 4 đến 63 ký tự + ĐƯỢC RỒI + Nhập sai mã PIN! + Huỷ + Kết nối khóa bảo mật của bạn với cổng USB hoặc kết nối bằng cáp USB. Nếu khóa của bạn có nút hoặc đĩa vàng, hãy nhấn vào đó ngay. + \ No newline at end of file diff --git a/play-services-location/core/base/src/main/kotlin/org/microg/gms/location/LocationSettings.kt b/play-services-location/core/base/src/main/kotlin/org/microg/gms/location/LocationSettings.kt index fec222c200..e8c29cfe33 100644 --- a/play-services-location/core/base/src/main/kotlin/org/microg/gms/location/LocationSettings.kt +++ b/play-services-location/core/base/src/main/kotlin/org/microg/gms/location/LocationSettings.kt @@ -8,6 +8,7 @@ package org.microg.gms.location import android.content.ContentValues import android.content.Context import android.database.Cursor +import org.microg.gms.location.base.BuildConfig import org.microg.gms.settings.SettingsContract private const val PATH_GEOLOCATE = "/v1/geolocate" @@ -96,6 +97,6 @@ class LocationSettings(private val context: Context) { set(value) = setSettings { put(SettingsContract.Location.ONLINE_SOURCE, value) } var ichnaeaContribute: Boolean - get() = false - set(value) = Unit + get() = getSettings(SettingsContract.Location.ICHNAEA_CONTRIBUTE) { c -> c.getInt(0) != 0 } + set(value) = setSettings { put(SettingsContract.Location.ICHNAEA_CONTRIBUTE, value) } } \ No newline at end of file diff --git a/play-services-location/core/build.gradle b/play-services-location/core/build.gradle index bdcd04fe99..c86d0d6a5d 100644 --- a/play-services-location/core/build.gradle +++ b/play-services-location/core/build.gradle @@ -43,7 +43,6 @@ android { targetSdkVersion androidTargetSdk buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"\"" buildConfigField "boolean", "SHOW_NOTIFICATION_WHEN_NOT_PERMITTED", "false" - buildConfigField "boolean", "ALWAYS_LISTEN_GPS_PASSIVE", "true" } lintOptions { @@ -59,13 +58,11 @@ android { dimension 'target' buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"com.huawei.permission.sec.MDM.v2\"" buildConfigField "boolean", "SHOW_NOTIFICATION_WHEN_NOT_PERMITTED", "true" - buildConfigField "boolean", "ALWAYS_LISTEN_GPS_PASSIVE", "false" } "huaweilh" { dimension 'target' buildConfigField "String", "FORCE_SHOW_BACKGROUND_PERMISSION", "\"com.huawei.permission.sec.MDM.v2\"" buildConfigField "boolean", "SHOW_NOTIFICATION_WHEN_NOT_PERMITTED", "true" - buildConfigField "boolean", "ALWAYS_LISTEN_GPS_PASSIVE", "false" } } diff --git a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/NetworkLocationService.kt b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/NetworkLocationService.kt index 60082080ee..8a1036943c 100644 --- a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/NetworkLocationService.kt +++ b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/NetworkLocationService.kt @@ -64,10 +64,11 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta private var lastCellLocation: Location? = null private var lastLocation: Location? = null - private val gpsLocationListener by lazy { LocationListenerCompat { onNewGpsLocation(it) } } + private val passiveLocationListener by lazy { LocationListenerCompat { onNewPassiveLocation(it) } } @GuardedBy("gpsLocationBuffer") private val gpsLocationBuffer = LinkedList() + private var passiveListenerActive = false private var currentLocalMovingWifi: WifiDetails? = null private var lastLocalMovingWifiLocationCandidate: Location? = null @@ -88,15 +89,31 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta putExtra(EXTRA_CONFIGURATION, CONFIGURATION_FIELD_ONLINE_SOURCE) }) } + } + + private fun updatePassiveGpsListenerRegistration() { try { getSystemService()?.let { locationManager -> - LocationManagerCompat.requestLocationUpdates( - locationManager, - LocationManager.GPS_PROVIDER, - LocationRequestCompat.Builder(LocationRequestCompat.PASSIVE_INTERVAL).setMinUpdateIntervalMillis(GPS_PASSIVE_INTERVAL).build(), - gpsLocationListener, - handlerThread.looper - ) + if ((settings.cellLearning || settings.wifiLearning) && (highPowerIntervalMillis != Long.MAX_VALUE)) { + if (!passiveListenerActive) { + LocationManagerCompat.requestLocationUpdates( + locationManager, + LocationManager.PASSIVE_PROVIDER, + LocationRequestCompat.Builder(LocationRequestCompat.PASSIVE_INTERVAL) + .setQuality(LocationRequestCompat.QUALITY_LOW_POWER) + .setMinUpdateIntervalMillis(GPS_PASSIVE_INTERVAL) + .build(), + passiveLocationListener, + handlerThread.looper + ) + passiveListenerActive = true + } + } else { + if (passiveListenerActive) { + LocationManagerCompat.removeUpdates(locationManager, passiveLocationListener) + passiveListenerActive = false + } + } } } catch (e: SecurityException) { Log.d(TAG, "GPS location retriever not initialized due to lack of permission") @@ -181,6 +198,8 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta handler.postDelayed(highPowerScanRunnable, nextHighPowerRequestIn) } } + + updatePassiveGpsListenerRegistration() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -539,8 +558,8 @@ class NetworkLocationService : LifecycleService(), WifiDetailsCallback, CellDeta } } - private fun onNewGpsLocation(location: Location) { - if (location.accuracy > GPS_PASSIVE_MIN_ACCURACY) return + private fun onNewPassiveLocation(location: Location) { + if (location.provider != LocationManager.GPS_PROVIDER || location.accuracy > GPS_PASSIVE_MIN_ACCURACY) return synchronized(gpsLocationBuffer) { if (gpsLocationBuffer.isNotEmpty() && gpsLocationBuffer.last.elapsedMillis < SystemClock.elapsedRealtime() - GPS_BUFFER_SIZE * GPS_PASSIVE_INTERVAL) { gpsLocationBuffer.clear() diff --git a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/FusedLocationProviderService.kt b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/FusedLocationProviderService.kt index 445986a8a8..c9b4a10999 100644 --- a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/FusedLocationProviderService.kt +++ b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/FusedLocationProviderService.kt @@ -5,10 +5,12 @@ package org.microg.gms.location.provider +import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Intent import android.location.Criteria import android.location.Location +import android.os.Binder import android.os.Build.VERSION.SDK_INT import android.util.Log import androidx.core.location.LocationRequestCompat @@ -23,29 +25,39 @@ import kotlin.math.max class FusedLocationProviderService : IntentLocationProviderService() { override fun extractLocation(intent: Intent): Location? = LocationResult.extractResult(intent)?.lastLocation - override fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent?) { - val intervalMillis = if (currentRequest?.reportLocation == true) { - max(currentRequest.interval, minIntervalMillis) - } else { - Long.MAX_VALUE - } + @SuppressLint("MissingPermission") + override fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent) { + val intervalMillis = max(currentRequest?.interval ?: Long.MAX_VALUE, minIntervalMillis) val request = LocationRequest.Builder(intervalMillis) if (SDK_INT >= 31 && currentRequest != null) { - request.setPriority(when(currentRequest.quality) { - LocationRequestCompat.QUALITY_LOW_POWER -> Priority.PRIORITY_LOW_POWER - LocationRequestCompat.QUALITY_HIGH_ACCURACY -> Priority.PRIORITY_HIGH_ACCURACY + request.setPriority(when { + currentRequest.interval == LocationRequestCompat.PASSIVE_INTERVAL -> Priority.PRIORITY_PASSIVE + currentRequest.isLowPower -> Priority.PRIORITY_LOW_POWER + currentRequest.quality == LocationRequestCompat.QUALITY_LOW_POWER -> Priority.PRIORITY_LOW_POWER + currentRequest.quality == LocationRequestCompat.QUALITY_HIGH_ACCURACY -> Priority.PRIORITY_HIGH_ACCURACY else -> Priority.PRIORITY_BALANCED_POWER_ACCURACY }) request.setMaxUpdateDelayMillis(currentRequest.maxUpdateDelayMillis) + request.setWorkSource(currentRequest.workSource) + } else { + request.setPriority(when { + currentRequest?.interval == LocationRequestCompat.PASSIVE_INTERVAL -> Priority.PRIORITY_PASSIVE + else -> Priority.PRIORITY_BALANCED_POWER_ACCURACY + }) + } + if (SDK_INT >= 29 && currentRequest != null) { + request.setBypass(currentRequest.isLocationSettingsIgnored) } + val identity = Binder.clearCallingIdentity() try { LocationServices.getFusedLocationProviderClient(this).requestLocationUpdates(request.build(), pendingIntent) } catch (e: SecurityException) { Log.d(TAG, "Failed requesting location updated", e) } + Binder.restoreCallingIdentity(identity) } - override fun stopIntentUpdated(pendingIntent: PendingIntent?) { + override fun stopIntentUpdated(pendingIntent: PendingIntent) { LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(pendingIntent) } @@ -59,7 +71,7 @@ class FusedLocationProviderService : IntentLocationProviderService() { get() = "fused" companion object { - private const val MIN_INTERVAL_MILLIS = 20000L + private const val MIN_INTERVAL_MILLIS = 1000L private const val MIN_REPORT_MILLIS = 1000L private val PROPERTIES = ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE) } diff --git a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderPreTiramisu.kt b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderPreTiramisu.kt index f5bb9c67e0..39c8642601 100644 --- a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderPreTiramisu.kt +++ b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderPreTiramisu.kt @@ -46,8 +46,8 @@ class IntentLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu { private val reportAgainRunnable = Runnable { reportAgain() } private fun updateRequest() { - if (enabled) { - service.requestIntentUpdated(currentRequest, pendingIntent) + if (enabled && pendingIntent != null) { + service.requestIntentUpdated(currentRequest, pendingIntent!!) reportAgain() } } @@ -55,6 +55,7 @@ class IntentLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu { override fun dump(writer: PrintWriter) { writer.println("Enabled: $enabled") writer.println("Current request: $currentRequest") + if (SDK_INT >= 31) writer.println("Current work source: ${currentRequest?.workSource}") writer.println("Last reported: $lastReportedLocation") writer.println("Last report time: ${lastReportTime.formatRealtime()}") } @@ -90,8 +91,8 @@ class IntentLocationProviderPreTiramisu : AbstractLocationProviderPreTiramisu { override fun disable() { synchronized(this) { - if (!enabled) throw IllegalStateException() - service.stopIntentUpdated(pendingIntent) + if (!enabled || pendingIntent == null) throw IllegalStateException() + service.stopIntentUpdated(pendingIntent!!) pendingIntent?.cancel() pendingIntent = null currentRequest = null diff --git a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderService.kt b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderService.kt index 2761b9aa40..f0483d9dd7 100644 --- a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderService.kt +++ b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/IntentLocationProviderService.kt @@ -34,9 +34,9 @@ abstract class IntentLocationProviderService : Service() { handler = Handler(handlerThread.looper) } - abstract fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent?) + abstract fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent) - abstract fun stopIntentUpdated(pendingIntent: PendingIntent?) + abstract fun stopIntentUpdated(pendingIntent: PendingIntent) abstract fun extractLocation(intent: Intent): Location? diff --git a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/NetworkLocationProviderService.kt b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/NetworkLocationProviderService.kt index 2c96eeb79d..4231a2f03d 100644 --- a/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/NetworkLocationProviderService.kt +++ b/play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/provider/NetworkLocationProviderService.kt @@ -22,7 +22,7 @@ class NetworkLocationProviderService : IntentLocationProviderService() { extras?.remove(LOCATION_EXTRA_PRECISION) } - override fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent?) { + override fun requestIntentUpdated(currentRequest: ProviderRequestUnbundled?, pendingIntent: PendingIntent) { val forceNow: Boolean val intervalMillis: Long if (currentRequest?.reportLocation == true) { @@ -48,7 +48,7 @@ class NetworkLocationProviderService : IntentLocationProviderService() { startService(intent) } - override fun stopIntentUpdated(pendingIntent: PendingIntent?) { + override fun stopIntentUpdated(pendingIntent: PendingIntent) { val intent = Intent(ACTION_NETWORK_LOCATION_SERVICE) intent.`package` = packageName intent.putExtra(EXTRA_PENDING_INTENT, pendingIntent) diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt index eb98ca0f49..1cb966b42a 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/DeviceOrientationManager.kt @@ -118,6 +118,7 @@ class DeviceOrientationManager(private val context: Context, override val lifecy synchronized(appOpsLock) { val newAppOps = mutableSetOf() for (request in requests.values) { + if (request.clientIdentity.isSelfUser()) continue newAppOps.add(request.clientIdentity) } Log.d(TAG, "Updating app ops for device orientation, change attribution to: ${newAppOps.map { it.packageName }.joinToString().takeIf { it.isNotEmpty() } ?: "none"}") diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt index 36d1af5a4f..7b671113f5 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationManager.kt @@ -13,8 +13,7 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.location.Location -import android.location.LocationManager.GPS_PROVIDER -import android.location.LocationManager.NETWORK_PROVIDER +import android.location.LocationManager.* import android.os.* import android.os.Build.VERSION.SDK_INT import android.util.Log @@ -53,10 +52,12 @@ class LocationManager(private val context: Context, override val lifecycle: Life private val requestManager by lazy { LocationRequestManager(context, lifecycle, postProcessor, database) { updateLocationRequests() } } private val gpsLocationListener by lazy { LocationListenerCompat { updateGpsLocation(it) } } private val networkLocationListener by lazy { LocationListenerCompat { updateNetworkLocation(it) } } + private val settings by lazy { LocationSettings(context) } private var boundToSystemNetworkLocation: Boolean = false private val activePermissionRequestLock = Mutex() private var activePermissionRequest: Deferred? = null private var lastGpsLocation: Location? = null + private var lastNetworkLocation: Location? = null private var currentGpsInterval: Long = -1 private var currentNetworkInterval: Long = -1 @@ -80,7 +81,7 @@ class LocationManager(private val context: Context, override val lifecycle: Life } val permissionGranularity = context.granularityFromPermission(clientIdentity) var effectiveGranularity = getEffectiveGranularity(request.granularity, permissionGranularity) - if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(clientIdentity.packageName)) effectiveGranularity = GRANULARITY_COARSE + if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(clientIdentity.packageName) && !clientIdentity.isSelfUser()) effectiveGranularity = GRANULARITY_COARSE val returnedLocation = if (effectiveGranularity > permissionGranularity) { // No last location available at requested granularity due to lack of permission null @@ -98,7 +99,7 @@ class LocationManager(private val context: Context, override val lifecycle: Life processedLocation } } - database.noteAppLocation(clientIdentity.packageName, returnedLocation) + if (!clientIdentity.isSelfUser()) database.noteAppLocation(clientIdentity.packageName, returnedLocation) return returnedLocation?.let { Location(it).apply { provider = "fused" } } } @@ -194,7 +195,7 @@ class LocationManager(private val context: Context, override val lifecycle: Life private fun updateLocationRequests() { val gpsInterval = when { - !BuildConfig.ALWAYS_LISTEN_GPS_PASSIVE && deviceOrientationManager.isActive -> min(requestManager.intervalMillis, DEVICE_ORIENTATION_INTERVAL) + deviceOrientationManager.isActive -> min(requestManager.intervalMillis, DEVICE_ORIENTATION_INTERVAL) requestManager.priority == PRIORITY_HIGH_ACCURACY && requestManager.granularity == GRANULARITY_FINE -> requestManager.intervalMillis else -> Long.MAX_VALUE } @@ -207,7 +208,7 @@ class LocationManager(private val context: Context, override val lifecycle: Life deviceOrientationManager.isActive -> DEVICE_ORIENTATION_INTERVAL else -> Long.MAX_VALUE } - val lowPower = requestManager.granularity <= GRANULARITY_COARSE || requestManager.priority >= Priority.PRIORITY_LOW_POWER + val lowPower = requestManager.granularity <= GRANULARITY_COARSE || requestManager.priority >= Priority.PRIORITY_LOW_POWER || (requestManager.priority >= Priority.PRIORITY_BALANCED_POWER_ACCURACY && requestManager.intervalMillis >= BALANCE_LOW_POWER_INTERVAL) if (context.hasNetworkLocationServiceBuiltIn() && currentNetworkInterval != networkInterval) { val intent = Intent(ACTION_NETWORK_LOCATION_SERVICE) @@ -243,6 +244,14 @@ class LocationManager(private val context: Context, override val lifecycle: Life } if (!context.hasNetworkLocationServiceBuiltIn() && LocationManagerCompat.hasProvider(locationManager, NETWORK_PROVIDER) && currentNetworkInterval != networkInterval) { boundToSystemNetworkLocation = true + if (networkInterval == Long.MAX_VALUE) { + // Fetch last location from GPS, just to make sure we already considered it + try { + locationManager.getLastKnownLocation(NETWORK_PROVIDER)?.let { updateNetworkLocation(it) } + } catch (e: SecurityException) { + // Ignore + } + } try { val quality = if (lowPower) QUALITY_LOW_POWER else QUALITY_BALANCED_POWER_ACCURACY locationManager.requestSystemProviderUpdates(NETWORK_PROVIDER, networkInterval, quality, networkLocationListener) @@ -258,9 +267,6 @@ class LocationManager(private val context: Context, override val lifecycle: Life if (interval != Long.MAX_VALUE) { Log.d(TAG, "Request updates for $provider at interval ${interval}ms") LocationManagerCompat.requestLocationUpdates(this, provider, Builder(interval).setQuality(quality).build(), listener, context.mainLooper) - } else if (BuildConfig.ALWAYS_LISTEN_GPS_PASSIVE && provider == GPS_PROVIDER) { - Log.d(TAG, "Request updates for $provider passively") - LocationManagerCompat.requestLocationUpdates(this, provider, Builder(PASSIVE_INTERVAL).setQuality(QUALITY_LOW_POWER).setMinUpdateIntervalMillis(MAX_FINE_UPDATE_INTERVAL).build(), listener, context.mainLooper) } else { Log.d(TAG, "Remove updates for $provider") LocationManagerCompat.removeUpdates(this, listener) @@ -288,14 +294,15 @@ class LocationManager(private val context: Context, override val lifecycle: Life } } - fun updateGpsLocation(location: Location) { + private fun updateGpsLocation(location: Location) { + if (location.provider != GPS_PROVIDER) return lastGpsLocation = location lastLocationCapsule.updateFineLocation(location) sendNewLocation() updateLocationRequests() } - fun sendNewLocation() { + private fun sendNewLocation() { lifecycleScope.launchWhenStarted { requestManager.processNewLocation(lastLocationCapsule) } @@ -382,5 +389,6 @@ class LocationManager(private val context: Context, override val lifecycle: Life const val DEVICE_ORIENTATION_INTERVAL = 10_000L const val NETWORK_OFF_GPS_AGE = 5000L const val NETWORK_OFF_GPS_ACCURACY = 10f + const val BALANCE_LOW_POWER_INTERVAL = 30_000L } } \ No newline at end of file diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt index 5e25af8763..ca770689d1 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt @@ -88,7 +88,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl try { val startedHolder = holder?.update(callback, request) ?: LocationRequestHolder(context, clientIdentity, request, callback, null).start().also { var effectiveGranularity = it.effectiveGranularity - if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(it.clientIdentity.packageName)) effectiveGranularity = GRANULARITY_COARSE + if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(it.clientIdentity.packageName) && !clientIdentity.isSelfUser()) effectiveGranularity = GRANULARITY_COARSE val lastLocation = lastLocationCapsule.getLocation(effectiveGranularity, request.maxUpdateAgeMillis) if (lastLocation != null) it.processNewLocation(lastLocation) } @@ -121,7 +121,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl pendingIntentRequests[pendingIntent] = LocationRequestHolder(context, clientIdentity, request, null, pendingIntent).start().also { cacheManager.add(it.asParcelable()) { it.pendingIntent == pendingIntent } var effectiveGranularity = it.effectiveGranularity - if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(it.clientIdentity.packageName)) effectiveGranularity = GRANULARITY_COARSE + if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(it.clientIdentity.packageName) && !clientIdentity.isSelfUser()) effectiveGranularity = GRANULARITY_COARSE val lastLocation = lastLocationCapsule.getLocation(effectiveGranularity, request.maxUpdateAgeMillis) if (lastLocation != null) it.processNewLocation(lastLocation) } @@ -147,11 +147,11 @@ class LocationRequestManager(private val context: Context, override val lifecycl for ((key, holder) in map) { try { var effectiveGranularity = holder.effectiveGranularity - if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(holder.clientIdentity.packageName)) effectiveGranularity = GRANULARITY_COARSE + if (effectiveGranularity == GRANULARITY_FINE && database.getForceCoarse(holder.clientIdentity.packageName) && !holder.clientIdentity.isSelfUser()) effectiveGranularity = GRANULARITY_COARSE val location = lastLocationCapsule.getLocation(effectiveGranularity, holder.maxUpdateDelayMillis) postProcessor.process(location, effectiveGranularity, holder.clientIdentity.isGoogle(context))?.let { if (holder.processNewLocation(it)) { - database.noteAppLocation(holder.clientIdentity.packageName, it) + if (!holder.clientIdentity.isSelfUser()) database.noteAppLocation(holder.clientIdentity.packageName, it) updated.add(key) } } @@ -214,7 +214,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl val newAppOps = mutableMapOf() val merged = binderRequests.values + pendingIntentRequests.values for (request in merged) { - if (request.effectivePriority >= PRIORITY_PASSIVE) continue + if (request.effectivePriority >= PRIORITY_PASSIVE || request.clientIdentity.isSelfUser()) continue if (!newAppOps.containsKey(request.clientIdentity)) { newAppOps[request.clientIdentity] = request.effectiveHighPower } else if (request.effectiveHighPower) { @@ -420,7 +420,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl get() = request.maxUpdates - updates val timePendingMillis: Long get() = request.durationMillis - (SystemClock.elapsedRealtime() - start) - var workSource: WorkSource = WorkSource(request.workSource).also { WorkSourceUtil.add(it, clientIdentity.uid, clientIdentity.packageName) } + var workSource: WorkSource = WorkSource(request.workSource).also { if (!clientIdentity.isSelfUser()) WorkSourceUtil.add(it, clientIdentity.uid, clientIdentity.packageName) } private set val effectiveHighPower: Boolean get() = request.intervalMillis < 60000 || effectivePriority == PRIORITY_HIGH_ACCURACY @@ -431,7 +431,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl this.request = request this.start = SystemClock.elapsedRealtime() this.updates = 0 - this.workSource = WorkSource(request.workSource).also { WorkSourceUtil.add(it, clientIdentity.uid, clientIdentity.packageName) } + this.workSource = WorkSource(request.workSource).also { if (!clientIdentity.isSelfUser()) WorkSourceUtil.add(it, clientIdentity.uid, clientIdentity.packageName) } if (changedGranularity) { if (!context.checkAppOpForEffectiveGranularity(clientIdentity, effectiveGranularity)) throw RuntimeException("Lack of permission") } diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/extensions.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/extensions.kt index 7799f5af26..3f51ce91d5 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/extensions.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/extensions.kt @@ -68,6 +68,7 @@ fun ILocationCallback.redirectCancel(fusedCallback: IFusedLocationProviderCallba fun ClientIdentity.isGoogle(context: Context) = PackageUtils.isGooglePackage(context, packageName) fun ClientIdentity.isSelfProcess() = pid == Process.myPid() +fun ClientIdentity.isSelfUser() = uid == Process.myUid() fun Context.granularityFromPermission(clientIdentity: ClientIdentity): @Granularity Int = when (PackageManager.PERMISSION_GRANTED) { packageManager.checkPermission(Manifest.permission.ACCESS_FINE_LOCATION, clientIdentity.packageName) -> Granularity.GRANULARITY_FINE @@ -77,13 +78,13 @@ fun Context.granularityFromPermission(clientIdentity: ClientIdentity): @Granular fun LocationRequest.verify(context: Context, clientIdentity: ClientIdentity) { GranularityUtil.checkValidGranularity(granularity) - if (isBypass) { + if (isBypass && !clientIdentity.isSelfUser()) { val permission = if (SDK_INT >= 33) "android.permission.LOCATION_BYPASS" else Manifest.permission.WRITE_SECURE_SETTINGS if (context.checkPermission(permission, clientIdentity.pid, clientIdentity.uid) != PackageManager.PERMISSION_GRANTED) { throw SecurityException("Caller must hold $permission for location bypass") } } - if (impersonation != null) { + if (impersonation != null && !clientIdentity.isSelfUser()) { Log.w(TAG, "${clientIdentity.packageName} wants to impersonate ${impersonation!!.packageName}. Ignoring.") } diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationPreferencesFragment.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationPreferencesFragment.kt index b0fd7305e5..41eb4c269a 100644 --- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationPreferencesFragment.kt +++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/ui/LocationPreferencesFragment.kt @@ -36,6 +36,7 @@ import org.microg.gms.location.core.R import org.microg.gms.location.manager.LocationAppsDatabase import org.microg.gms.location.network.OnlineSource import org.microg.gms.location.network.effectiveEndpoint +import org.microg.gms.location.network.onlineSource import org.microg.gms.ui.AppIconPreference import org.microg.gms.ui.buildAlertDialog import org.microg.gms.ui.getApplicationInfoIfExists @@ -189,7 +190,7 @@ class LocationPreferencesFragment : PreferenceFragmentCompat() { view.setPadding(0, 16.dp, 0, 0) view.orientation = LinearLayout.VERTICAL val settings = LocationSettings(requireContext()) - val currentSourceId = settings.onlineSourceId + val currentSourceId = settings.onlineSource?.id val unselectHandlerMap = mutableMapOf Unit>() var selectedSourceId = currentSourceId val customView = layoutInflater.inflate(R.layout.preference_location_custom_url, null) diff --git a/play-services-location/core/src/main/res/values-ar/strings.xml b/play-services-location/core/src/main/res/values-ar/strings.xml index a6b3daec93..e7c28c9f71 100644 --- a/play-services-location/core/src/main/res/values-ar/strings.xml +++ b/play-services-location/core/src/main/res/values-ar/strings.xml @@ -1,2 +1,53 @@ - \ No newline at end of file + + الموقع + الوصول الأخير + موقع شبكة الجوال + محلل العناوين + طلب من موزيلا + موقع شبكة الـ Wi-Fi + طلب من خدمة عبر الإنترنت + حصول على موقع شبكة الـ Wi-Fi من خدمة تحديد الموقع من موزيلا. + حصول على موقع شبكة الـ Wi-Fi من خدمة تحديد الموقع عبر الإنترنت. + طلب من نقطة الاتصال + تخزين مواقع الـ Wi-Fi محلياً عند استخدام GPS. + طلب من موزيلا + حصول على مواقع الأبراج الخلوية لشبكة الجوال من خدمة تحديد الموقع من موزيلا. + تذكُّر من GPS + طلب من خدمة عبر الإنترنت + حصول على مواقع الأبراج الخلوية لشبكة الجوال من خدمة تحديد الموقع من خدمة عبر اﻹنترنت. + تذكُّر من GPS + تخزين مواقع شبكة الجوال محلياً عند استخدام GPS. + استخدام نوميناتيم + حلّ العناوين باستخدام خدمة نوميناتيم من خريطة الشارع المفتوحة. + تطبيقات ذات إمكانية الوصول إلى الموقع + آخر وصول: %1$s + استخدام الموقع التقريبي دائمًا + قم دائمًا بتقديم المواقع التقريبي لهذا التطبيق، مع تجاهل مستوى الإذن الخاص به. + للحصول على تجربة أفضل، قم بتشغيل موقع الجهاز، والذي يستخدم خدمة تحديد الموقع لمايكرو-جي + للمتابعة، قم بتشغيل موقع الجهاز، والذي يستخدم خدمة تحديد الموقع لمايكرو-جي + سيحتاج جهازك إلى: + استخدام GPS، وWi-Fi، وشبكة الجوال، والمستشعرات + منح أذونات الموقع لخدمة مايكرو-جي + لا شكرًا + موافق + إعادة الضبط + استخدام خدمة تحديد الموقع لمايكرو-جي؛ كجزء من هذه الخدمة، قد يقوم مايكرو-جي بجمع بيانات الموقع بشكل دوري ومُخفي للمصدر واستخدام هذه البيانات لتحسين دقة الموقع والخدمات القائمة على الموقع. + تعيين رابط الخدمة + اختر خدمة تحديد الموقع عبر الإنترنت + رابط مخصص للخدمة + مخصص + يسمح هذا بتعيين رابط مخصص للخدمة. القيم غير الصالحة قد تؤدي إلى عدم استجابة خدمات الموقع. + الشروط / الخصوصية + مقترح + استيراد أو تصدير بيانات الموقع + التهيئة مطلوبة + لمتابعة استخدام خدمات الموقع عبر الإنترنت، تحتاج إلى تحديد خدمة بيانات الموقع. + تصدير قاعدة بيانات مواقع الـ Wi-Fi المحلية + تصدير قاعدة بيانات مواقع الأبراج الخلوية المحلية + تم استيراد %1$d سِجِل. + حصول على موقع شبكة الـ Wi-Fi مباشرةً من نقاط الاتصال المدعومة عند الاتصال بها. + لمزيد من التفاصيل، انتقل إلى إعدادات الموقع. + يتم إلحاق المسار \"v1/geolocate/\" تلقائيًا. إذا كان موفِّر الموقع يتطلب مفتاحًا، فيمكن إلحاقه كمعلمة استعلام إلى جذر الرابط. + استيراد بيانات الموقع من ملف + \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-fr/strings.xml b/play-services-location/core/src/main/res/values-fr/strings.xml index c25a660b73..1fde9e4fcd 100644 --- a/play-services-location/core/src/main/res/values-fr/strings.xml +++ b/play-services-location/core/src/main/res/values-fr/strings.xml @@ -1,9 +1,53 @@ - - Location + + Localisation Accès récents - Localisation Wi-Fi - Localisation du réseau mobile + Localisation basée sur le Wi-Fi + Localisation basée sur le réseau mobile Résolution d\'adresses - Requête depuis Mozilla + Obtention depuis Mozilla + Pour continuer, activez la localisation de l\'appareil, le service de localisation de microG sera utilisé + Obtention via un service en ligne + Récupérer la localisation basée sur le Wi-Fi depuis un service de localisation en ligne. + Récupérer la localisation basée sur le Wi-Fi depuis une connexion hot-spot supportée. + Obtention depuis Mozilla + Mémorisation depuis le GPS + Enregistrer localement la localisation des réseaux Wi-Fi lorsque le GPS est utilisé. + Récupérer la localisation basée sur le réseau mobile depuis le service de localisation de Mozilla. + Obtention depuis un service en ligne + Récupérer la localisation basée sur le réseau mobile depuis un service de localisation en ligne. + Mémorisation depuis le GPS + Enregistrer localement la localisation des tours du réseau mobile lorsque le GPS est utilisé. + Utiliser Nominatim + Déterminer les adresses en utilisant Nominatim d\'OpenStreetMap. + Applis ayant accès à la localisation + Toujours fournir une localisation approximative à cette appli, et ignorer son niveau de permission. + Dernier accès : %1$s + Forcer la localisation approximative + GPS, Wi-Fi, réseaux mobiles et capteurs + Accorder aux services microG l\'accès à la localisation + Votre appareil va utiliser : + Non merci + OK + Personnalisée + Choisir le service de localisation en ligne + Termes et confidentialité + Suggéré + Importer/exporter les données de localisation + Configuration requise + Pour continuer à utiliser les services de localisation en ligne, vous devez choisir un service de données de localisation. + Exporter la base de données locale de la localisation basée sur le Wi-Fi + Exporter la base de données locale de la localisation des tours du réseau mobile + Importer les données de localisation depuis un fichier + %1$d enregistrements importés. + Pour une meilleure expérience, activez la localisation de l\'appareil, le service de localisation de microG sera utilisé + Pour plus de détails, consultez les paramètres de localisation. + Obtention via hot-spot + Utiliser le service de localisation de microG. Via l\'utilisation de ce service, microG peut collecter périodiquement les données de localisation et les utiliser de façon anonyme pour améliorer la précision de la localisation et des services basés sur la localisation. + Le chemin de géolocalisation /v1/ est automatiquement attaché. Si le fournisseur de localisation exige une clé, elle peut être rattachée en tant que paramètre de la requête à l\'URL racine. + Configurer l\'URL du service + Réinitialisation + Ceci permet de modifier l\'URL du service. Une valeur incorrecte peut causer une perte de réactivité des services de localisation, voire leur indisponibilité totale. + URL personnalisée du service + Récupérer la localisation basée sur le Wi-Fi depuis le service de localisation de Mozilla. \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-is/strings.xml b/play-services-location/core/src/main/res/values-is/strings.xml index a6b3daec93..2afef667bd 100644 --- a/play-services-location/core/src/main/res/values-is/strings.xml +++ b/play-services-location/core/src/main/res/values-is/strings.xml @@ -1,2 +1,42 @@ - \ No newline at end of file + + Staðsetning + Nýlegur aðgangur + Wi-Fi staðsetning + Staðsetning farsímanets + Biðja um frá netþjónustu + Biðja um frá Mozilla + Biðja um frá netþjónustu + Tækið þitt mun þurfa að: + Nota GPS, Wi‑Fi, farsímanet og skynjara + Til að skoða nánar skaltu fara í staðsetningarstillingar. + Veldu staðsetningaþjónustu + Flytja staðsetningargögn inn úr skrá + Flutti inn %1$d færslur. + Uppfletting heimilisfanga + Biðja um frá Mozilla + Biðja um frá tengipunkti + Muna frá GPS + Muna frá GPS + Nota Nominatim + Forrit með aðgang að staðsetningu + Þvinga grófa staðsetningu + Virkja heimildir fyrir staðsetningu til microG-þjónustu + Nei takk + Í lagi + Stilla slóð þjónustu + Endurstilla + Sérsniðin slóð þjónustu + Sérsniðið + Skilmálar og meðferð persónuupplýsinga + Tillaga + Flytja inn/út staðsetningargögn + Staðfestingar krafist + Fletta upp heimilisföngum með OpenStreetMap Nominatim. + Síðasti aðgangur: %1$s + Til að hlutirnir virki betur, skaltu kveikja á staðsetningu tækisins, sem notar staðsetningaþjónustu microG + Flytja út gagnagrunn Wi-Fi-staðsetninga af tölvunni + Til að halda áfram, skaltu kveikja á staðsetningu tækisins, sem notar staðsetningaþjónustu microG + Til að halda áfram að nota staðsetningaþjónustur á netinu, þarftu að velja þjónustu fyrir staðsetningagögn. + Flytja út gagnagrunn endurvarpastaðsetninga af tölvunni + \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-it/strings.xml b/play-services-location/core/src/main/res/values-it/strings.xml index ce7c50bd5e..03f83aaf03 100644 --- a/play-services-location/core/src/main/res/values-it/strings.xml +++ b/play-services-location/core/src/main/res/values-it/strings.xml @@ -9,7 +9,7 @@ Ottieni una geolocalizzazione basata sul Wi-Fi da Mozilla Location Service. Salva sul dispositivo informazioni sul posizionamento delle reti Wi-Fi quando il GPS è in uso. Richiedi a Mozilla - Salva sul dispositivo informazioni sul posizionamento delle antenne delle reti cellulari quando il GPS è in uso. + Memorizza localmente le posizioni basate sulla rete mobile quando si utilizza il GPS. Deduci dal GPS Deduci dal GPS Ottieni una geolocalizzazione basata sul Wi-Fi direttamente da un hotspot che la supporta, quando connesso. @@ -20,7 +20,7 @@ Cerca gli indirizzi usando OpenStreetMap Nominatim. Geolocalizzazione basata sul Wi-Fi Ricerca degli indirizzi - Ottieni informazioni sul posizionamento delle antenne delle reti cellulari da Mozilla Location Service. + Ottieni la posizione basata sulle torri cellulari della rete mobile da Mozilla Location Service. OK No, grazie Per ulteriori dettagli, vai alle impostazioni di localizzazione. @@ -35,4 +35,19 @@ Configura l\'URL del servizio Questo consente di impostare un URL del servizio personalizzato. Valori non validi possono rendere i servizi di localizzazione instabili o completamente non disponibili. URL del servizio personalizzato + Richiesta dal servizio online + Ottieni la posizione basata su Wi-Fi dal servizio di localizzazione online. + Richiesta dal servizio online + Ottieni la posizione basata sulle torri cellulari della rete mobile dal servizio di localizzazione online. + Seleziona il servizio di localizzazione online + Termini / Privacy + Suggeriti + Personalizzato + Importare o esportare dati sulla posizione + Configurazione richiesta + Per continuare a utilizzare i servizi di localizzazione online, è necessario selezionare un servizio dati sulla localizzazione. + Esportare il database della posizione Wi-Fi locale + Importa i dati sulla posizione da file + Esportare il database locale delle posizioni basate sulle torri cellulari + Importati il %1$d dei registri. \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-pt-rBR/strings.xml b/play-services-location/core/src/main/res/values-pt-rBR/strings.xml index 5be3d10e10..cdbca64e28 100644 --- a/play-services-location/core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-location/core/src/main/res/values-pt-rBR/strings.xml @@ -6,10 +6,10 @@ Localização por redes móveis Tradutor de endereço Solicitar da Mozilla - Solicitar localização baseada em redes Wi-Fi próximas pelos serviços de localização da Mozilla. + Obter localização baseada em Wi-Fi dos serviços de localização da Mozilla. Solicitar do ponto de acesso - Solicitar a localização por Wi-Fi diretamente pelos pontos de acesso que suportam isso quando conectado. - Lembrar pelo GPS + Obter localização baseada em Wi-Fi diretamente de pontos de acessos suportados quando conectado. + Lembrar do GPS Guardar localmente informações de localização de redes Wi-Fi quando o GPS é usado. Solicitar da Mozilla Solicitar a localização de redes móveis pelo serviço de localização da Mozilla. @@ -39,7 +39,7 @@ Solicitar de serviço online Obter localizações de torres de redes móveis de um serviço online. Customizado - Selecionar serviço de localização online + Escolher serv. de localização online Termos / Privacidade Configuração obrigatória Exportar banco de dados local de localização Wi-Fi @@ -47,7 +47,7 @@ Importar dados de localização por arquivo Obter localização baseada em Wi-Fi através de um serviço online. Sugerido - Importar ou exportar dados de localização + Importar/exportar dados de localização Para continuar usando os serviços de localização online, você precisa selecionar um serviço de dados de localização. %1$d relatórios importados. \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-sr/strings.xml b/play-services-location/core/src/main/res/values-sr/strings.xml index 7521783fb3..4f0cb6cda6 100644 --- a/play-services-location/core/src/main/res/values-sr/strings.xml +++ b/play-services-location/core/src/main/res/values-sr/strings.xml @@ -29,7 +29,7 @@ Користите GPS, Wi-Fi, мобилне податке и сензоре За детаље, идите на подешавања локације. За боље искуство, укључите локацију уређаја који користи услугу локације microG-а - Конфигуриши URL услуге + Подеси URL услуге Ресетуј Ово омогућава постављање прилагођеног URL-а услуге. Неважеће вредности могу довести до тога да услуге локације не реагују или буду потпуно недоступне. Путања /v1/geolocate се аутоматски додаје. Ако пружалац локације захтева кључ, он се може додати као параметар упита основном URL-у. @@ -49,5 +49,5 @@ Избор онлајн услуге локације Услови / Приватност Увоз или извоз података о локацији - Неопходна конфигурација + Неопходно подешавање \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-sv/strings.xml b/play-services-location/core/src/main/res/values-sv/strings.xml index f75a4493de..f2f74f0651 100644 --- a/play-services-location/core/src/main/res/values-sv/strings.xml +++ b/play-services-location/core/src/main/res/values-sv/strings.xml @@ -35,4 +35,19 @@ Återställ På detta sätt kan du ange en anpassad tjänst-URL. Ogiltiga värden kan leda till att platstjänster inte svarar eller är helt otillgängliga. Anpassad service-URL + Förfrågan från online-tjänst + Förfrågan från online-tjänst + Hämta Wi-Fi-baserad plats från platstjänst online. + Hämta mobila nätverks mastplatser från platstjänster online. + Anpassat + Välj platstjänst online + Villkor / Integritet + Konfiguration krävs + För att fortsätta använda platstjänster online måste du välja en platsdatatjänst. + Exportera lokal Wi-Fi-platsdatabas + Exportera lokal platsdatabas för mobilmaster + Importera platsdata från fil + Importerade %1$d poster. + Föreslagen + Importera eller exportera platsdata \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-th/strings.xml b/play-services-location/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..f89dea0ced --- /dev/null +++ b/play-services-location/core/src/main/res/values-th/strings.xml @@ -0,0 +1,53 @@ + + + ตำแหน่งเครือข่ายมือถือ + ดึงข้อมูลตำแหน่งที่ใช้ Wi-Fi จาก บริการค้นหาตำแหน่ง ของ Mozilla + การร้องขอจากบริการออนไลน์ + คำขอจาก Hotspot + จดจำจาก GPS + จัดเก็บตำแหน่ง Wi-Fi ไว้ในเครื่องเมื่อใช้งาน GPS + คำขอจาก Mozilla + ดึงข้อมูลตำแหน่งเสาสัญญาณเครือข่ายมือถือจากบริการค้นหาตำแหน่งของ Mozilla Location Service + ดึงตำแหน่งจากเสาของเครือข่ายมือถือจากบริการระบุตำแหน่งออนไลน์ + แก้ไขที่อยู่โดยใช้ พิกัดภูมิศาสตร์ ของ OpenStreetMap + ใช้งานพิกัดทางภูมิศาสตร์ + เข้าถึงล่าสุด: %1$s + ตำแหน่งคร่าวๆ + ส่งตำแหน่งคร่าวๆ ให้กับแอปนี้เสมอ โดยไม่สนใจระดับของการอนุญาต + ใช้ GPS, Wi-Fi, เครือข่ายมือถือ และเซ็นเซอร์ + ให้สิทธิ์เข้าถึงตำแหน่งแก่บริการ microG + สำหรับรายละเอียด ให้ไปที่การตั้งค่าตำแหน่ง + ไม่ล่ะ ขอบคุณ + ตกลง + กำหนดค่า URL + ล้างค่า + ที่ตั้ง + การเข้าถึงล่าสุด + ตำแหน่งของ Wi-Fi + ตัวแก้ไขที่อยู่ + การร้องขอจาก Mozilla + ดึงข้อมูลตำแหน่งที่ใช้ Wi-Fi จากบริการระบุตำแหน่งออนไลน์ + ดึงข้อมูลตำแหน่ง Wi-Fi โดยตรงจาก Hotspot ที่รองรับเมื่อเชื่อมต่อ + คำขอจากบริการออนไลน์ + หากต้องการดำเนินการต่อ ให้เปิดตำแหน่งอุปกรณ์ซึ่งใช้บริการตำแหน่งของ microG + อุปกรณ์ของคุณจะต้อง: + จัดเก็บตำแหน่งเครือข่ายมือถือในเครื่องเมื่อใช้ GPS + จดจำจาก GPS + แอพที่เข้าถึงตำแหน่งได้ + เพื่อประสบการณ์ที่ดีขึ้น ให้เปิดตำแหน่งอุปกรณ์ซึ่งใช้บริการตำแหน่งของ microG + ใช้บริการระบุตำแหน่ง microG ในฐานะส่วนหนึ่งของบริการนี้ microG อาจรวบรวมข้อมูลตำแหน่งเป็นระยะ ๆ และใช้ข้อมูลนี้ในลักษณะที่ไม่ระบุชื่อเพื่อปรับปรุงความแม่นยำของตำแหน่งและบริการตามตำแหน่ง + ช่วยให้สามารถตั้งค่า URL ที่กำหนดเองได้ URL ค่าที่ไม่ถูกต้องอาจส่งผลให้บริการระบุตำแหน่งไม่ตอบสนองหรือไม่พร้อมใช้งาน + เส้นทาง /v1/geolocate จะถูกผนวกโดยอัตโนมัติ หากผู้ให้บริการตำแหน่งต้องการคีย์ ก็สามารถผนวกเป็นพารามิเตอร์การค้นหาในจุดแรกสุดของ URL ได้ + URL บริการที่กำหนดเอง + กำหนดเอง + เลือกบริการระบุตำแหน่งออนไลน์ + เงื่อนไข/ความเป็นส่วนตัว + ข้อเสนอแนะ + นำเข้าหรือส่งออกข้อมูลตำแหน่งที่ตั้ง + จำเป็นต้องมีการกำหนดค่า + หากต้องการใช้งานบริการระบุตำแหน่งออนไลน์ต่อไป คุณจะต้องเลือกบริการข้อมูลระบุตำแหน่ง + ส่งออกฐานข้อมูลตำแหน่ง Wi-Fi ในพื้นที่ + ส่งออกฐานข้อมูลตำแหน่งเสาโทรศัพท์มือถือในพื้นที่ + นำเข้าข้อมูลตำแหน่งจากไฟล์ + นำเข้า %1$d บันทึก + \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-tr/strings.xml b/play-services-location/core/src/main/res/values-tr/strings.xml index a6b3daec93..791bcfc7c2 100644 --- a/play-services-location/core/src/main/res/values-tr/strings.xml +++ b/play-services-location/core/src/main/res/values-tr/strings.xml @@ -1,2 +1,53 @@ - \ No newline at end of file + + Konum + Wi-Fi üzerinden konum + Mobil ağ üzerinden konum + Adres çözümleyicisi + Mozilla\'dan iste + Mozilla Konum Hizmeti\'nden Wi-Fi tabanlı konumu iste. + Çevrimiçi servisten iste + GPS\'ten hatırla + Mobil erişim noktalarından iste + GPS kullanıldığında Wi-Fi konumlarını yerelde sakla. + Çevrimiçi servisten Wi-Fi tabanlı konumu iste. + Çevrimiçi servisten mobil ağ istasyonlarının konumunu iste. + Nominatim\'i kullan + Konum erişimine sahip uygulamalar + GPS kullanıldığında mobil şebeke konumlarını yerel olarak sakla. + Cihazınız şun(lar)a ihtiyaç duyuyor: + Yaklaşık konum vermeye zorla + Uygulamaya verilen izinden bağımsız olarak, bu uygulamaya her zaman yaklaşık konum sağla. + Daha iyi bir deneyim için, microG\'nin konum servislerinden yararlanan cihaz konumunu etkinleştirin + GPS, Wi-Fi, mobil ağlar ve sensörleri kullanmasına + Detaylar için, konum ayarlarına gidin. + Hayır, teşekkürler + Tamam + Hizmet sağlayıcı URL\'sini değiştir + Sıfırla + Bu, özel bir servis sağlayıcısı URL\'si belirtmenize olanak tanır. Yanlış ayarlama sonucunda, konum servisleri yanıt vermeyi durdurabilir veya tamamen mevcut olmayabilir. + microG konum servislerini kullanın; bu servisin bir parçası olarak, microG periyodik olarak konum verisi toplayabilir ve bu verileri anonim bir şekilde konum doğruluğunu iyileştirmek ve daha iyi bir konum tabanlı servis sunmak için kullanabilir. + Koşullar / Gizlilik + Önerilen + Belirtilen URL\'ye /v1/geolocate yolu kendiliğinden eklenecektir. Konum sağlayıcısı, eğer bir erişim anahtarına ihtiyaç duyuyor ise kök URL\'ye sorgu parametresi olarak eklenebilir. + Çevrimiçi konum hizmetlerini kullanmak için öncelikle bir konum veri sağlayıcısı seçmeniz gerekiyor. + Yerel Wi-Fi konum veritabanını dışarı aktar + Yerel baz istasyonu konum veritabanını dışarı aktar + Dosyadan konum verisini içeri aktar + %1$d kayıt içe aktarıldı. + Son erişim + Çevrimiçi servisten iste + Son erişim: %1$s + Mozilla\'dan iste + Mozilla Konum Hizmeti\'nden baz istasyonların konumunu iste. + Özel + Konum verisini içe veya dışarı aktar + microG Servisleri\'ne konum izninin verilmesine + Özel hizmet URL\'si + Ayarlama gerekiyor + Çevrimiçi konum sağlayıcısı seç + Adresleri OpenStreetMap Nominatim hizmeti ile çöz. + GPS\'ten hatırla + Bağlanıldığında Wi-Fi konumunu direkt olarak desteklenen mobil erişim noktalarından (hotspot) al. + Devam etmek için, microG\'nin konum servislerinden yararlanan cihaz konumunu etkinleştirin + \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-uk/strings.xml b/play-services-location/core/src/main/res/values-uk/strings.xml index 189f4d23e7..0a0ed1f252 100644 --- a/play-services-location/core/src/main/res/values-uk/strings.xml +++ b/play-services-location/core/src/main/res/values-uk/strings.xml @@ -35,4 +35,19 @@ Шлях /v1/geolocate буде додано автоматично. Якщо постачальник послуг з визначення місцяположення вимагає ключ, його можна додати як параметр запиту до кореневої URL-адреси. Власна URL-адреса служби Надайте сервісу microG доступ до місцеположення + Отримувати місцеположення на основі Wi-Fi мережевого сервісу визначення розташування. + Запит від мережевого сервісу + Отримувати розташування веж мобільного зв\'язку з мережевого сервісу визначення розташування. + Власний + Виберіть мережевий сервіс визначення розташування + Умови / Приватність + Імпортувати або експортувати дані про розташування + Експортувати локальну базу даних розташування Wi-Fi + Імпортувати дані про розташування з файлу + Імпортовано %1$d записів. + Щоб продовжити користуватися мережевими сервісами визначення розташування, вам потрібно вибрати сервіс даних про розташування. + Запит від мережевого сервісу + Експортувати локальну базу даних розташування стільникових веж + Запропоновано + Потрібне налаштування \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-vi/strings.xml b/play-services-location/core/src/main/res/values-vi/strings.xml index c6ff4c44e3..15092ff4ea 100644 --- a/play-services-location/core/src/main/res/values-vi/strings.xml +++ b/play-services-location/core/src/main/res/values-vi/strings.xml @@ -1,5 +1,53 @@ - + Vị trí - Các yêu cầu vị trí gần đây + Truy cập gần đây + Lấy vị trí dựa trên Wi-Fi từ dịch vụ định vị trực tuyến. + Yêu cầu từ dịch vụ trực tuyến + Lưu trữ vị trí Wi-Fi cục bộ khi sử dụng GPS. + Yêu cầu từ Mozilla + Lấy vị trí trạm phát di động từ Dịch vụ vị trí Mozilla. + Yêu cầu từ dịch vụ trực tuyến + Lấy vị trí trạm phát sóng di động từ dịch vụ định vị trực tuyến. + Ghi nhớ từ GPS + Lưu trữ vị trí mạng di động cục bộ khi sử dụng GPS. + Luôn trả về vị trí không chính xác cho ứng dụng này, bỏ qua mức độ quyền của ứng dụng. + Để có trải nghiệm tốt hơn, hãy bật chức năng định vị thiết bị và sử dụng dịch vụ định vị của microG + Cấp quyền vị trí cho Dịch vụ microG + Tuỳ chỉnh + Chọn dịch vụ định vị trực tuyến + Điều khoản/Quyền riêng tư + Đề xuất + Nhập hoặc xuất dữ liệu vị trí + Cấu hình yêu cầu + Để tiếp tục sử dụng dịch vụ định vị trực tuyến, bạn cần chọn một dịch vụ dữ liệu vị trí. + Xuất cơ sở dữ liệu vị trí Wi-Fi cục bộ + Xuất cơ sở dữ liệu vị trí trạm phát sóng di động địa phương + Nhập dữ liệu vị trí từ tệp + Đã nhập %1$d bản ghi. + Vị trí Wi-Fi + Vị trí dữ liệu di động + Bộ phân giải địa chỉ + Ghi nhớ từ GPS + Yêu cầu từ Mozilla + Lấy vị trí dựa trên Wi-Fi từ Dịch vụ vị trí Mozilla. + Yêu cầu từ Hotspot + Lấy vị trí Wi-Fi trực tiếp từ các điểm phát sóng được hỗ trợ khi đã kết nối. + Phân giải địa chỉ bằng OpenStreetMap Nominatim. + Sử dụng Nominatim + Lần truy cập cuối: %1$s + Buộc vị trí không chính xác + Ứng dụng có quyền truy cập vị trí + Thiết bị của bạn cần phải: + Để có thể tiếp tục, hãy bật chức năng định vị thiết bị và sử dụng dịch vụ định vị của microG + Sử dụng GPS, Wi‑Fi, dữ liệu di động và cảm biến + Để biết chi tiết, hãy vào phần cài đặt vị trí. + Không, cảm ơn + Sử dụng dịch vụ định vị microG; như một phần của dịch vụ này, microG có thể thu thập dữ liệu vị trí theo định kỳ và sử dụng dữ liệu này theo cách ẩn danh để cải thiện độ chính xác của vị trí và các dịch vụ dựa trên vị trí. + ĐƯỢC RỒI + Cấu hình URL dịch vụ + URL dịch vụ tùy chỉnh + Đặt lại + Điều này cho phép thiết lập URL dịch vụ tùy chỉnh. Các giá trị không hợp lệ có thể khiến dịch vụ vị trí không phản hồi hoặc hoàn toàn không khả dụng. + Đường dẫn /v1/geocate được tự động thêm vào. Nếu nhà cung cấp vị trí yêu cầu khóa, khóa này có thể được thêm vào như một tham số truy vấn vào URL gốc. \ No newline at end of file diff --git a/play-services-location/core/src/main/res/values-zh-rCN/strings.xml b/play-services-location/core/src/main/res/values-zh-rCN/strings.xml index a4066e2b04..ac80672208 100644 --- a/play-services-location/core/src/main/res/values-zh-rCN/strings.xml +++ b/play-services-location/core/src/main/res/values-zh-rCN/strings.xml @@ -35,4 +35,19 @@ /v1/geolocate路径会自动添加。如果位置提供商需要密钥,则可以将其作为查询参数附加到根URL。 自定义服务URL 向 microG 服务授予位置权限 + 从在线位置服务获取基于Wi-Fi的位置。 + 来自在线服务的请求 + 从在线位置服务获取基于移动网络的位置。 + 自定义 + 选择在线定位服务 + 条款/隐私 + 建议 + 导入或导出位置数据 + 需要的配置 + 导出本地基站位置数据库 + 从文件导入位置数据 + 已导入 %1$d 条记录。 + 来自在线服务的请求 + 要继续使用在线位置服务,您需要选择一个位置数据服务。 + 导出本地Wi-Fi位置数据库 \ No newline at end of file diff --git a/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderClient.java b/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderClient.java index 033eaa669b..0be024e85f 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderClient.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderClient.java @@ -17,6 +17,7 @@ import android.provider.Settings; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresPermission; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApi; @@ -109,7 +110,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task getCurrentLocation(int priority, CancellationToken cancellationToken); + public abstract Task getCurrentLocation(int priority, @Nullable CancellationToken cancellationToken); /** * Returns a single location fix representing the best estimate of the current location of the device. This may return a historical location if a recent @@ -122,7 +123,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task getCurrentLocation(CurrentLocationRequest request, CancellationToken cancellationToken); + public abstract Task getCurrentLocation(@NonNull CurrentLocationRequest request, @Nullable CancellationToken cancellationToken); /** * Returns the most recent historical location currently available according to the given request. Will return null if no matching historical location is @@ -130,7 +131,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task getLastLocation(LastLocationRequest request); + public abstract Task getLastLocation(@NonNull LastLocationRequest request); /** * Returns the most recent historical location currently available. Will return null if no historical location is available. The historical location may @@ -154,19 +155,19 @@ protected FusedLocationProviderClient(Context context) { * Removes all location updates for the given listener. */ @NonNull - public abstract Task removeLocationUpdates(LocationListener listener); + public abstract Task removeLocationUpdates(@NonNull LocationListener listener); /** * Removes all location updates for the given callback. */ @NonNull - public abstract Task removeLocationUpdates(LocationCallback callback); + public abstract Task removeLocationUpdates(@NonNull LocationCallback callback); /** * Removes all location updates for the given pending intent. */ @NonNull - public abstract Task removeLocationUpdates(PendingIntent pendingIntent); + public abstract Task removeLocationUpdates(@NonNull PendingIntent pendingIntent); /** * Requests location updates with the given request and results delivered to the given listener on the specified {@link Looper}. A @@ -184,7 +185,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper); + public abstract Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationListener listener, @Nullable Looper looper); /** * Requests location updates with the given request and results delivered to the given callback on the specified {@link Executor}. @@ -199,7 +200,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task requestLocationUpdates(LocationRequest request, Executor executor, LocationCallback callback); + public abstract Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationCallback callback); /** * Requests location updates with the given request and results delivered to the given listener on the specified {@link Executor}. @@ -208,7 +209,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task requestLocationUpdates(LocationRequest request, Executor executor, LocationListener listener); + public abstract Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationListener listener); /** * Requests location updates with the given request and results delivered to the given callback on the specified @@ -229,7 +230,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task requestLocationUpdates(LocationRequest request, LocationCallback callback, Looper looper); + public abstract Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationCallback callback, @Nullable Looper looper); /** * Requests location updates with the given request and results delivered via the specified {@link PendingIntent}. A previous @@ -256,7 +257,7 @@ protected FusedLocationProviderClient(Context context) { */ @NonNull @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) - public abstract Task requestLocationUpdates(LocationRequest request, PendingIntent pendingIntent); + public abstract Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull PendingIntent pendingIntent); /** * Sets the mock location of the Fused Location Provider. @@ -272,7 +273,8 @@ protected FusedLocationProviderClient(Context context) { * @param location valid location to set as the next FLP location * @throws SecurityException if security requirements are not met */ - public abstract Task setMockLocation(Location location); + @NonNull + public abstract Task setMockLocation(@NonNull Location location); /** * Sets whether or not the Fused Location Provider is in mock mode. @@ -289,5 +291,6 @@ protected FusedLocationProviderClient(Context context) { * @param mockMode the mock mode state to set for the Fused Location Provider APIs * @throws SecurityException if security requirements are not met */ + @NonNull public abstract Task setMockMode(boolean mockMode); } diff --git a/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderApiImpl.java b/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderApiImpl.java index 3ebffee547..eac50bb816 100644 --- a/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderApiImpl.java +++ b/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderApiImpl.java @@ -76,10 +76,11 @@ public void run(LocationClientImpl client) throws RemoteException { @Override public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, LocationCallback callback, Looper looper) { + Looper currentLooper = looper == null ? Looper.myLooper() : looper; return callVoid(client, new Runnable() { @Override public void run(LocationClientImpl client) throws RemoteException { - client.requestLocationUpdates(request, callback, looper); + client.requestLocationUpdates(request, callback, currentLooper); } }); } @@ -88,10 +89,11 @@ public void run(LocationClientImpl client) throws RemoteException { public PendingResult requestLocationUpdates(GoogleApiClient client, final LocationRequest request, final LocationListener listener, final Looper looper) { + Looper currentLooper = looper == null ? Looper.myLooper() : looper; return callVoid(client, new Runnable() { @Override public void run(LocationClientImpl client) throws RemoteException { - client.requestLocationUpdates(request, listener, looper); + client.requestLocationUpdates(request, listener, currentLooper); } }); } diff --git a/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java b/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java index cf00155a88..a557fceca4 100644 --- a/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java +++ b/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java @@ -10,7 +10,9 @@ import android.location.Location; import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.gms.location.*; import com.google.android.gms.tasks.CancellationToken; import com.google.android.gms.tasks.Task; @@ -26,6 +28,7 @@ public FusedLocationProviderClientImpl(Context context) { super(context); } + @NonNull public Task flushLocations() { return scheduleTask((ReturningGoogleApiCall) (client) -> null); } @@ -38,16 +41,17 @@ public Task getCurrentLocation(int priority, CancellationToken cancell @NonNull @Override - public Task getCurrentLocation(CurrentLocationRequest request, CancellationToken cancellationToken) { + public Task getCurrentLocation(@NonNull CurrentLocationRequest request, CancellationToken cancellationToken) { return null; } @NonNull @Override - public Task getLastLocation(LastLocationRequest request) { + public Task getLastLocation(@NonNull LastLocationRequest request) { return null; } + @NonNull public Task getLastLocation() { return scheduleTask((ReturningGoogleApiCall) LocationClientImpl::getLastLocation); } @@ -58,53 +62,67 @@ public Task getLocationAvailability() { return scheduleTask((ReturningGoogleApiCall) LocationClientImpl::getLocationAvailability); } + @NonNull @Override - public Task removeLocationUpdates(LocationListener listener) { + public Task removeLocationUpdates(@NonNull LocationListener listener) { return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.removeLocationUpdates(listener)); } + @NonNull @Override - public Task removeLocationUpdates(PendingIntent pendingIntent) { + public Task removeLocationUpdates(@NonNull PendingIntent pendingIntent) { return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.removeLocationUpdates(pendingIntent)); } + @NonNull @Override - public Task requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper) { - return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, listener, looper)); + public Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationListener listener, @Nullable Looper looper) { + Looper currentLooper = looper == null ? Looper.myLooper() : looper; + if (currentLooper == null) throw new IllegalStateException("looper is null and the calling thread has not called Looper.prepare()"); + return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, listener, currentLooper)); } + @NonNull @Override - public Task requestLocationUpdates(LocationRequest request, Executor executor, LocationCallback callback) { + public Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationCallback callback) { return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, executor, callback)); } + @NonNull @Override - public Task requestLocationUpdates(LocationRequest request, Executor executor, LocationListener listener) { + public Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull Executor executor, @NonNull LocationListener listener) { return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, executor, listener)); } + @NonNull @Override - public Task requestLocationUpdates(LocationRequest request, LocationCallback callback, Looper looper) { - return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, callback, looper)); + public Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull LocationCallback callback, Looper looper) { + Looper currentLooper = looper == null ? Looper.myLooper() : looper; + if (currentLooper == null) throw new IllegalStateException("looper is null and the calling thread has not called Looper.prepare()"); + return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, callback, currentLooper)); } + @NonNull @Override - public Task requestLocationUpdates(LocationRequest request, PendingIntent pendingIntent) { + public Task requestLocationUpdates(@NonNull LocationRequest request, @NonNull PendingIntent pendingIntent) { return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.requestLocationUpdates(request, pendingIntent)); } + @NonNull @Override - public Task setMockLocation(Location location) { - return null; + public Task setMockLocation(@NonNull Location location) { + return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.setMockLocation(location)); } + @NonNull @Override public Task setMockMode(boolean mockMode) { - return null; + return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.setMockMode(mockMode)); } + @NonNull @Override - public Task removeLocationUpdates(LocationCallback callback) { + public Task removeLocationUpdates(@NonNull LocationCallback callback) { return scheduleTask((VoidReturningGoogleApiCall) (client) -> client.removeLocationUpdates(callback)); } } diff --git a/play-services-location/src/main/java/org/microg/gms/location/LocationClientImpl.java b/play-services-location/src/main/java/org/microg/gms/location/LocationClientImpl.java index 7b4b62ddf2..bb302fffc5 100644 --- a/play-services-location/src/main/java/org/microg/gms/location/LocationClientImpl.java +++ b/play-services-location/src/main/java/org/microg/gms/location/LocationClientImpl.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.util.Log; +import androidx.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.ILocationListener; @@ -113,7 +114,7 @@ public void requestLocationUpdates(LocationRequest request, PendingIntent pendin getServiceInterface().requestLocationUpdatesWithIntent(request, pendingIntent); } - public void requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper) throws RemoteException { + public void requestLocationUpdates(LocationRequest request, LocationListener listener, @NonNull Looper looper) throws RemoteException { final Handler handler = new Handler(looper); requestLocationUpdates(request, handler::post, listener); } @@ -135,7 +136,7 @@ public void cancel() throws RemoteException { getServiceInterface().requestLocationUpdatesWithPackage(request, listenerMap.get(listener), getContext().getPackageName()); } - public void requestLocationUpdates(LocationRequest request, LocationCallback callback, Looper looper) throws RemoteException { + public void requestLocationUpdates(LocationRequest request, LocationCallback callback, @NonNull Looper looper) throws RemoteException { final Handler handler = new Handler(looper); requestLocationUpdates(request, handler::post, callback); } diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt index aa5911f805..aef3dd8e36 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt @@ -18,8 +18,6 @@ import android.widget.LinearLayout import androidx.collection.LongSparseArray import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.unwrap -import com.google.android.gms.maps.GoogleMap.MAP_TYPE_HYBRID -import com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE import com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.internal.* @@ -87,7 +85,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private var storedMapType: Int = options.mapType val waitingCameraUpdates = mutableListOf() - var locationEnabled: Boolean = false + private val controlLayerRun = Runnable { refreshContainerLayer(false) } private var markerId = 0L val markers = mutableMapOf() @@ -102,7 +100,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) this.view = object : FrameLayout(mapContext) {} } - override fun getCameraPosition(): CameraPosition? = map?.cameraPosition?.toGms() + override fun getCameraPosition(): CameraPosition { + return map?.cameraPosition?.toGms() ?: CameraPosition(LatLng(0.0, 0.0), 0f, 0f, 0f) + } override fun getMaxZoomLevel(): Float = toHmsZoom(map?.maxZoomLevel ?: 18.toFloat()) override fun getMinZoomLevel(): Float = toHmsZoom(map?.minZoomLevel ?: 3.toFloat()) @@ -306,8 +306,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) internalOnInitializedCallbackList.add(it.getMapReadyCallback()) } - override fun getProjection(): IProjectionDelegate = - map?.projection?.let { ProjectionImpl(it) } ?: DummyProjection() + override fun getProjection(): IProjectionDelegate { + return map?.projection?.let { ProjectionImpl(it) } ?: DummyProjection() + } override fun setOnCameraChangeListener(listener: IOnCameraChangeListener?) = afterInitialize { Log.d(TAG, "setOnCameraChangeListener"); @@ -493,6 +494,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) cameraMoveStartedListener = listener hmap.setOnCameraMoveStartedListener { try { + Log.d(TAG, "setCameraMoveStartedListener: ") cameraMoveStartedListener?.onCameraMoveStarted(it) } catch (e: Exception) { Log.w(TAG, e) @@ -505,15 +507,12 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) cameraMoveListener = listener it.setOnCameraMoveListener { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if(mapView != null){ - if(mapView!!.parent != null){ - mapView!!.parent.onDescendantInvalidated(mapView!!,mapView!!) - } - } - } + Log.d(TAG, "setOnCameraMoveListener: ") + view.removeCallbacks(controlLayerRun) + refreshContainerLayer(true) cameraMoveListener?.onCameraMove() cameraChangeListener?.onCameraChange(map?.cameraPosition?.toGms()) + view.postDelayed(controlLayerRun, 200) } catch (e: Exception) { Log.w(TAG, e) } @@ -525,6 +524,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) cameraMoveCanceledListener = listener it.setOnCameraMoveCanceledListener { try { + Log.d(TAG, "setOnCameraMoveCanceledListener: ") cameraMoveCanceledListener?.onCameraMoveCanceled() } catch (e: Exception) { Log.w(TAG, e) @@ -535,13 +535,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setCameraIdleListener(listener: IOnCameraIdleListener?) = afterInitialize { Log.d(TAG, "onCameraIdle: successful") cameraIdleListener = listener - it.setOnCameraIdleListener { - try { - cameraIdleListener?.onCameraIdle() - } catch (e: Exception) { - Log.w(TAG, e) - } - } } override fun getTestingHelper(): IObjectWrapper? { @@ -619,14 +612,14 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } catch (e: Exception) { Log.w(TAG, e) } - } - map.setOnCameraIdleListener { + try { cameraIdleListener?.onCameraIdle() } catch (e: Exception) { Log.w(TAG, e) } } + map.setOnCameraMoveListener { try { cameraMoveListener?.onCameraMove() @@ -804,6 +797,24 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } } + private fun refreshContainerLayer(hide: Boolean = false) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + view.onDescendantInvalidated(mapView!!, mapView!!) + } + val parentView = view.parent?.parent + if (parentView != null) { + if (parentView is ViewGroup) { + for (i in 0 until parentView.childCount) { + val viewChild = parentView.getChildAt(i) + // Uber is prone to route drift, so here we hide the corresponding layer + if (viewChild::class.qualifiedName == "com.ubercab.android.map.fu") { + viewChild.visibility = if (hide) View.INVISIBLE else View.VISIBLE + } + } + } + } + } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = if (super.onTransact(code, data, reply, flags)) { Log.d(TAG, "onTransact: $code, $data, $flags") diff --git a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleLocationEngine.kt b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleLocationEngine.kt index 654a1407ba..43a6f53b2b 100644 --- a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleLocationEngine.kt +++ b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleLocationEngine.kt @@ -44,7 +44,7 @@ class GoogleLocationEngine(context: Context) : LocationEngine { .setMinUpdateDistanceMeters(request.displacement) .setMinUpdateIntervalMillis(request.fastestInterval) .setMaxUpdateDelayMillis(request.maxWaitTime) - .build(), listenerMap[callback], looper + .build(), listenerMap[callback]!!, looper ) } diff --git a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt index 129eec77b3..08cd716551 100644 --- a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +++ b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt @@ -448,17 +448,14 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG } private fun updateLocationEngineListener(myLocation: Boolean) { - val locationComponent = map?.locationComponent ?: return - if (locationComponent.isLocationComponentActivated) { - locationComponent.isLocationComponentEnabled = myLocation - if (myLocation) { - locationComponent.locationEngine?.requestLocationUpdates( - locationComponent.locationEngineRequest, - locationEngineCallback, - null - ) - } else { - locationComponent.locationEngine?.removeLocationUpdates(locationEngineCallback) + map?.locationComponent?.let { + if (it.isLocationComponentActivated) { + it.isLocationComponentEnabled = myLocation + if (myLocation) { + it.locationEngine?.requestLocationUpdates(it.locationEngineRequest, locationEngineCallback, Looper.getMainLooper()) + } else { + it.locationEngine?.removeLocationUpdates(locationEngineCallback) + } } } } @@ -829,10 +826,34 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG } } - override fun onResume() = mapView?.onResume() ?: Unit - override fun onPause() = mapView?.onPause() ?: Unit + override fun onResume() { + Log.d(TAG, "onResume") + if (!isStarted) { + // onStart was not called, invoke mapView.onStart() now + mapView?.onStart() + } + mapView?.onResume() + map?.locationComponent?.let { + if (it.isLocationComponentEnabled) { + it.locationEngine?.requestLocationUpdates(it.locationEngineRequest, locationEngineCallback, Looper.getMainLooper()) + } + } + } + override fun onPause() { + Log.d(TAG, "onPause") + map?.locationComponent?.let { + if (it.isLocationComponentEnabled) { + it.locationEngine?.removeLocationUpdates(locationEngineCallback) + } + } + mapView?.onPause() + if (!isStarted) { + // onStart was not called, invoke mapView.onStop() now + mapView?.onStop() + } + } override fun onDestroy() { - Log.d(TAG, "destroy"); + Log.d(TAG, "onDestroy"); userOnInitializedCallbackList.clear() lineManager?.onDestroy() lineManager = null @@ -858,11 +879,13 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG } override fun onStart() { + Log.d(TAG, "onStart") isStarted = true mapView?.onStart() } override fun onStop() { + Log.d(TAG, "onStop") isStarted = false mapView?.onStop() } diff --git a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt index 16f0471d8b..b9514a17d8 100644 --- a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt +++ b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt @@ -39,7 +39,12 @@ import kotlin.math.* */ fun IInfoWindowAdapter.getInfoWindowViewFor(marker: IMarkerDelegate, mapContext: MapContext): View? { - getInfoWindow(marker).unwrap()?.let { return it } + getInfoWindow(marker).unwrap()?.let { view -> + return view.apply { + // Remove any previous parents mistakenly added by the client + parent?.let { (it as ViewManager).removeView(this) } + } + } getInfoContents(marker).unwrap()?.let { view -> // Detach from previous BubbleLayout parent, if exists @@ -95,7 +100,7 @@ class InfoWindow internal constructor( /** * Close this [InfoWindow] if it is visible, otherwise calling this will do nothing. * - * @param silent `OnInfoWindowCloseListener` is only called if `silent` is not `false` + * @param silent `OnInfoWindowCloseListener` is only called if `silent` is `false` */ fun close(silent: Boolean = false) { if (isVisible) { diff --git a/play-services-nearby/core/src/main/res/values-cs/strings.xml b/play-services-nearby/core/src/main/res/values-cs/strings.xml index d48fed4f45..fe055d1c81 100644 --- a/play-services-nearby/core/src/main/res/values-cs/strings.xml +++ b/play-services-nearby/core/src/main/res/values-cs/strings.xml @@ -31,7 +31,7 @@ Exportovat nasbíraná ID pro rozšířenou analýzu v jiné aplikaci. Používat oznámení o možném kontaktu Zapnout oznámení o možném kontaktu? - Váš telefon potřebuje používat Bluetooth pro bezpečné sbírání a sdílení ID s ostatními telefony, které jsou v okolí. \u0020Aplikace %1$s vám může oznámit, zda jste se setkali s někým, kdo byl nahlášen jako pozitivní. \u0020Datum, doba trvání a síla signálu spojené s kontaktem budou sdíleny s touto aplikací. + Váš telefon potřebuje používat Bluetooth pro bezpečné sbírání a sdílení ID s ostatními telefony, které jsou v okolí. Aplikace %1$s vám může oznámit, zda jste se setkali s někým, kdo byl nahlášen jako pozitivní. Datum, doba trvání a síla signálu spojené s kontaktem budou sdíleny s touto aplikací. Zapnout Vypnout oznámení o možném kontaktu? Po vypnutí oznámení o možném kontaktu již nebudete upozorněni, zda jste se setkali s někým, kdo byl nahlášen jako pozitivní. diff --git a/play-services-nearby/core/src/main/res/values-es/strings.xml b/play-services-nearby/core/src/main/res/values-es/strings.xml index dd88fb3dac..5d739baf62 100644 --- a/play-services-nearby/core/src/main/res/values-es/strings.xml +++ b/play-services-nearby/core/src/main/res/values-es/strings.xml @@ -38,12 +38,8 @@ Eliminar de todas formas Exportar Exportar identificaciones recogidas para su análisis ampliado con otra aplicación. - La API de notificaciones de exposición permite que las aplicaciones le notifiquen si ha estado expuesto a alguien con diagnóstico positivo. -\n -\nLa fecha, la duración y la intensidad de la señal asociadas a una exposición se compartirán con la app correspondiente. - Mientras la API de notificación de exposición está activada, el dispositivo recopila de forma pasiva identificadores (denominados identificadores de proximidad móviles o RPI) de los dispositivos cercanos. -\n -\nCuando los propietarios de los dispositivos informan de un diagnóstico positivo, sus ID pueden compartirse. Su dispositivo comprueba si alguno de los ID diagnosticados conocidos coincide con alguno de los ID recopilados y calcula su riesgo de infección. + La API de notificaciones de exposición permite que las aplicaciones le notifiquen si ha estado expuesto a alguien con diagnóstico positivo.\n\nLa fecha, la duración y la intensidad de la señal asociadas a una exposición se compartirán con la app correspondiente. + Mientras la API de notificación de exposición está activada, el dispositivo recopila de forma pasiva identificadores (denominados identificadores de proximidad móviles o RPI) de los dispositivos cercanos.\n\nCuando los propietarios de los dispositivos informan de un diagnóstico positivo, sus ID pueden compartirse. Su dispositivo comprueba si alguno de los ID diagnosticados conocidos coincide con alguno de los ID recopilados y calcula su riesgo de infección. Usar Notificaciones de Exposición ¿Encender las Notificaciones de Exposición? El teléfono debe usar el Bluetooth para recopilar y compartir de manera segura las identificaciones con otros teléfonos que estén cerca. @@ -56,9 +52,7 @@ La fecha, la duración y la intensidad de la señal asociadas a una exposición Después de desactivar las Notificaciones de Exposición, ya no se le notificará cuando haya estado expuesto a alguien que haya informado de que ha sido diagnosticado como positivo. Apagar Comparte tus identificaciones con %1$s? - Tus identificaciones de los últimos 14 días se utilizarán para ayudar a notificar a otras personas que has estado cerca sobre una posible exposición. -\n -\nTu identidad o el resultado de la prueba no se compartirán con otras personas. + Tus identificaciones de los últimos 14 días se utilizarán para ayudar a notificar a otras personas que has estado cerca sobre una posible exposición.\n\nTu identidad o el resultado de la prueba no se compartirán con otras personas. Compartir %1$s necesita permisos adicionales. Conceder diff --git a/play-services-nearby/core/src/main/res/values-fr/strings.xml b/play-services-nearby/core/src/main/res/values-fr/strings.xml index a6b3daec93..d4444eb626 100644 --- a/play-services-nearby/core/src/main/res/values-fr/strings.xml +++ b/play-services-nearby/core/src/main/res/values-fr/strings.xml @@ -1,2 +1,68 @@ - \ No newline at end of file + + Notifications d\'exposition inactives + Malheureusement, votre appareil est seulement partiellement compatible avec les notifications d\'exposition. Vous pouvez être notifié des contacts à risque mais vous ne pourrez pas notifier les autres. + %1$d IDs dans la dernière heure + Le Bluetooth doit être activé pour recevoir les notifications d\'exposition. + L\'accès à la localisation est nécessaire pour recevoir les notifications d\'exposition. + Le Bluetooth et l\'accès à la localisation doivent être activés pour recevoir les notifications d\'exposition. + Les notifications d\'exposition nécessitent des permissions additionnelles pour fonctionner + Notifications d\'exposition + Ouvrir les paramètres de localisation + Activer le Bluetooth + Pour activer les notifications d\'exposition, ouvrez une appli les prenant en charge. + Malheureusement, votre appareil n\'est pas compatible avec les notifications d\'exposition. + Applis utilisant les notifications d\'exposition + IDs collectés + Il y a %1$d minutes environ + Traité %1$d clés de diagnostic. + %1$d expositions à risque rapportées : + Partager vos IDs avec %1$s ? + %1$s nécessite des autorisations supplémentaires. + Mis à jour : %1$s + %1$s, %2$s + %1$s IDs par heure + %1$s, score de risque%2$d + %1$d appelle à %2$s + %1$d IDs collectés + Votre téléphone a besoin d\'utiliser le Bluetooth pour collecter et partager de manière sécurisée les IDs avec les autres appareils à proximité. %1$s peut vous notifier si vous avez été exposé à une personne ayant indiqué un diagnostic positif. La date, la durée et la force du signal liées à une exposition seront partagées avec cette application. + En désactivant les Notifications d’exposition, vous ne serez plus informé si vous avez été exposé à une personne ayant indiqué un diagnostic positif. + Activer + Nouvelles autorisations nécessaires + ID actuellement diffusé + Moins de 5 minutes + Exposition distante + Exposition proche + Appuyez pour accorder les autorisations nécessaires aux Notifications d\'exposition + Expositions rapportées + Pas de rencontres à risque rapportées. + Usage de l\'API sur les 14 derniers jours + Note : Le score de risque est défini par l\'appli. Une valeur élevée peut faire référence à un risque faible et vice-versa. + Pas d\'enregistrements + Supprimer + Supprimer tous les IDs collectés + La suppression des IDs collectés rendra impossible de vous informer en cas de diagnostic positif d\'un contact sur les 14 derniers jours. + Supprimer quand même + Exporter + Exporter les IDs collectés pour analyse étendue via une autre appli. + L\'API Notifications d\'exposition permet aux applis de vous informer si vous avez été exposé à une personne ayant indiqué un diagnostique positif. +\n +\nLa date, la durée et la force du signal liées à une exposition seront partagées avec l\'appli correspondante. + Quand l\'API Notifications d\'exposition est activée, votre appareil collecte de manière passive les IDs (appelés identifiants roulants de proximité) des appareils à proximité. +\n +\nLorsque des utilisateurs indiquent un diagnostic positif, leur ID peut être partagé. Votre appareil compare les IDs collectés aux IDs diagnostiqués positifs connus et calcule votre risque d\'infection. + Utiliser les Notifications d\'exposition + Activer les Notifications d\'exposition ? + Activer + Désactiver les Notifications d\'exposition ? + Désactiver + Vos IDs des 14 derniers jours seront utilisés dans le but d\'informer les autres que vous avez été proche d\'une exposition potentielle. +\n +\nVotre identité ou vos résultats de test ne seront pas partagés avec d\'autres personnes. + Partager + Accorder + Le Bluetooth doit être activé. + L\'accès à la localisation est nécessaire. + On y est presque ! Vous aurez besoin d\'activer la localisation en arrière-plan en sélectionnant \"Toujours autorisé\" sur l\'écran suivant. Puis revenez en arrière. + Modifier les paramètres + \ No newline at end of file diff --git a/play-services-nearby/core/src/main/res/values-ja/strings.xml b/play-services-nearby/core/src/main/res/values-ja/strings.xml index a6b3daec93..0a4118f569 100644 --- a/play-services-nearby/core/src/main/res/values-ja/strings.xml +++ b/play-services-nearby/core/src/main/res/values-ja/strings.xml @@ -1,2 +1,7 @@ - \ No newline at end of file + + Bluetooth を有効にする + 位置情報の設定を開く + 収集されたID + 現在のブロードキャストID + \ No newline at end of file diff --git a/play-services-nearby/core/src/main/res/values-pl/strings.xml b/play-services-nearby/core/src/main/res/values-pl/strings.xml index 1b865b9a47..0a2d3822d1 100644 --- a/play-services-nearby/core/src/main/res/values-pl/strings.xml +++ b/play-services-nearby/core/src/main/res/values-pl/strings.xml @@ -53,7 +53,7 @@ Twoje identyfikatory w ciągu ostatnich 14 dni zostaną wykorzystane, aby pomóc powiadamiać innych o możliwym wystąpieniu narażenia na kontakt z wirusem. \n \nZarówno Twoja tożsamość jak i wynik testu nie zostanie udostępniona innym osobom. - Twój telefon musi korzystać z Bluetooth aby bezpiecznie zbierać i udostępniać identyfikatory do innych telefonów w pobliżu. „%1$s” może powiadomić cię o możliwym narażeniu na kontakt z osobami, których wynik testu na obecność wirusa był pozytywny. Data, okres i siła sygnału związana z narażeniem na kontakt zostanie udostępniona tej aplikacji. + Twój telefon musi korzystać z Bluetooth, aby bezpiecznie gromadzić i udostępniać identyfikatory innym telefonom znajdującym się w pobliżu. <xliff:g example=„Corona-Warn”>%1$s</xliff:g> może powiadomić cię o możliwym narażeniu na kontakt z osobami, których wynik testu na obecność wirusa był pozytywny. Data, czas trwania i siła sygnału związane z ekspozycją zostaną udostępnione aplikacji. Brak wpisów Usuń Wymagane nowe uprawnienia diff --git a/play-services-nearby/core/src/main/res/values-pt-rBR/strings.xml b/play-services-nearby/core/src/main/res/values-pt-rBR/strings.xml index e20a46fcce..fb7c63e454 100644 --- a/play-services-nearby/core/src/main/res/values-pt-rBR/strings.xml +++ b/play-services-nearby/core/src/main/res/values-pt-rBR/strings.xml @@ -18,7 +18,7 @@ Foram processadas %1$d chaves de diagnóstico. %1$s, chance de risco %2$d %1$s IDs por hora - Seu dispositivo precisa usar Bluetooth para coletar e compartilhar IDs com segurança para dispositivos por perto. \u0020%1$s pode te notificar se você foi exposto à alguém que foi diagnosticado positivo. \u0020A data, duração, e força do sinal associada com uma exposição será compartilhada com o app. + Seu dispositivo precisa usar Bluetooth para coletar e compartilhar IDs com segurança para dispositivos por perto. %1$s pode te notificar se você foi exposto à alguém que foi diagnosticado positivo. A data, duração, e força do sinal associada com uma exposição será compartilhada com o app. Notificações de exposição inativas O Bluetooth precisa estar ativado para receber notificações de exposição. Acesso à localização é necessário para receber notificações de exposição. diff --git a/play-services-nearby/core/src/main/res/values-ro/strings.xml b/play-services-nearby/core/src/main/res/values-ro/strings.xml index 63af794c61..65e4631ad0 100644 --- a/play-services-nearby/core/src/main/res/values-ro/strings.xml +++ b/play-services-nearby/core/src/main/res/values-ro/strings.xml @@ -46,7 +46,7 @@ În timp ce API-ul Exposure Notification este activat, dispozitivul colectează pasiv ID-uri (numite Rolling Proximity Identifiers sau RPI) de la dispozitivele din apropiere. \n \nCând proprietarii de dispozitive raportează că sunt diagnosticați pozitivi, ID-urile lor pot fi partajate. Dispozitivul verifică dacă vreunul dintre ID-urile diagnosticate cunoscute se potrivește cu oricare dintre ID-urile colectate și calculează riscul de infecție. - Telefonul trebuie să utilizeze Bluetooth pentru a colecta și a partaja ID-uri în siguranță cu alte telefoane din apropiere.%1$s poate anunța dacă ai fost expus la cineva care a raportat că a fost diagnosticat pozitiv. Data, durata și puterea semnalului asociate cu o expunere vor fi partajate cu aplicația. + Telefonul trebuie să utilizeze Bluetooth pentru a colecta și a partaja ID-uri în siguranță cu alte telefoane din apropiere. %1$s poate anunța dacă ai fost expus la cineva care a raportat că a fost diagnosticat pozitiv. Data, durata și puterea semnalului asociate cu o expunere vor fi partajate cu aplicația. Dezactivezi notificările de expunere? Oprire După dezactivarea notificărilor de expunere, nu vei mai fi notificat când ai fost expus la cineva care a raportat că a fost diagnosticat pozitiv. diff --git a/play-services-nearby/core/src/main/res/values-sr/strings.xml b/play-services-nearby/core/src/main/res/values-sr/strings.xml index 7aea5e51d5..4dfb13ae28 100644 --- a/play-services-nearby/core/src/main/res/values-sr/strings.xml +++ b/play-services-nearby/core/src/main/res/values-sr/strings.xml @@ -53,7 +53,7 @@ Ваши ID-ови из последњих 14 дана ће се користити за обавештавање других да сте били у близини о потенцијалној изложености. \n \nВаш идентитет или резултат теста се неће делити са другим људима. - Ваш телефон мора да користи Bluetooth за безбедно прикупљање и дељење ID-ова са другим телефонима који су у близини. %1$s може да вас обавести ако сте били изложени некоме ко је пријавио да је позитиван. Датум, трајање и јачина сигнала повезани са изложеношћу ће се делити са апликацијом. + Ваш телефон мора да користи Bluetooth за безбедно прикупљање и дељење ID-ова са другим телефонима који су у близини. %1$s може да вас обавести ако сте били изложени некоме ко је пријавио да је позитиван. Датум, трајање и јачина сигнала повезани са изложеношћу ће се делити са апликацијом. Нема записа Избриши Нове дозволе су неопходне diff --git a/play-services-nearby/core/src/main/res/values-sv/strings.xml b/play-services-nearby/core/src/main/res/values-sv/strings.xml index a903c103ee..1262bf3713 100644 --- a/play-services-nearby/core/src/main/res/values-sv/strings.xml +++ b/play-services-nearby/core/src/main/res/values-sv/strings.xml @@ -53,7 +53,7 @@ Dina ID:n från de senaste 14 dagarna kommer att användas för att informera andra i din närhet, om potentiell exponering. \n \nDin identitet eller testresultat kommer inte att delas med andra människor. - Din telefon måste använda Bluetooth för att säkert samla in och dela ID:n med andra telefoner som finns i närheten. %1$s kan meddela dig om du exponerats för någon som rapporterats vara positivt diagnostiserad. Datum, varaktighet och signalstyrka i samband med en exponering kommer att delas med appen. + Din telefon måste använda Bluetooth för att säkert samla in och dela ID:n med andra telefoner som finns i närheten. %1$s kan meddela dig om du exponerats för någon som rapporterats vara positivt diagnostiserad. Datum, varaktighet och signalstyrka i samband med en exponering kommer att delas med appen. Inga poster Ta bort Nya behörigheter krävs diff --git a/play-services-nearby/core/src/main/res/values-th/strings.xml b/play-services-nearby/core/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..9e423d1abd --- /dev/null +++ b/play-services-nearby/core/src/main/res/values-th/strings.xml @@ -0,0 +1,62 @@ + + + การแจ้งเตือนการสัมผัสไม่ได้ใช้งาน + จำเป็นต้องเปิดใช้งาน Bluetooth เพื่อรับการแจ้งเตือนการสัมผัส + จำเป็นต้องเข้าถึงตำแหน่งเพื่อรับการแจ้งเตือนการสัมผัส + จำเป็นต้องเปิดใช้งานบลูทูธและการเข้าถึงตำแหน่งเพื่อรับการแจ้งเตือนการสัมผัส + การแจ้งเตือนการสัมผัส + เพื่อเปิดการใช้งานการแจ้งเตือนการสัมผัส ให้เปิดแอปใด ๆ ที่รองรับ + เปิดใช้งาน Bluetooth + ขออภัย อุปกรณ์ของคุณเข้ากันได้กับการแจ้งเตือนความเสี่ยงเพียงบางส่วนเท่านั้น คุณสามารถรับการแจ้งเตือนเกี่ยวกับผู้ติดต่อที่มีความเสี่ยงได้ แต่จะไม่สามารถแจ้งเตือนผู้อื่นได้ + ขออภัย อุปกรณ์ของคุณไม่รองรับการแจ้งเตือนการสัมผัส + แอปกำลังใช้การแจ้งเตือนการสัมผัส + เก็บรวบรวมรหัสประจำตัว + รหัสที่กำลังกระจายสัญญาณในปัจจุบัน + การรายงานการสัมผัส + อัปเดต: %1$s + น้อยกว่า 5 นาที + เกี่ยวกับ %1$d นาที + การสัมผัสในบริเวณใกล้เคียง + การสัมผัสระยะไกล + %1$s, %2$s + ประมวลผล %1$dกุญแจสำหรับการวินิจฉัยแล้ว + รายงาน %1$dการเผชิญเหตุ: + การใช้งาน API ในช่วง 14 วันที่ผ่านมา + %1$d เรียกไปยัง %2$s + %1$s รหัสประจำตัวต่อชั่วโมง + ลบรหัสประจำตัวทั้งหมด + ลบต่อไป + ส่งออก + ส่งออก รหัสประจำตัว ที่เก็บรวบรวมไว้เพื่อการวิเคราะห์เพิ่มเติมด้วยแอปอื่น + ใช้งานการแจ้งเตือนการสัมผัส + เปิดใช้งานการแจ้งเตือนการสัมผัส ? + เปิด + ปิดการแจ้งเตือนการสัมผัส? + หลังจากปิดใช้งานการแจ้งเตือนการสัมผัสแล้ว คุณจะไม่ได้รับการแจ้งเตือนอีกต่อไปเมื่อคุณสัมผัสกับบุคคลที่รายงานว่าได้รับการวินิจฉัยว่าติดเชื้อ + ปิด + แบ่งปัน รหัสประจำตัว ของคุณกับ %1$s ? + ข้อมูลประจำตัวของคุณในช่วง 14 วันที่ผ่านมาจะถูกใช้เพื่อแจ้งให้ผู้อื่นทราบว่าคุณอยู่ใกล้ๆ และมีความเสี่ยงต่อการติดเชื้อ\n\nข้อมูลประจำตัวหรือผลการทดสอบของคุณจะไม่ถูกเปิดเผยให้ผู้อื่นทราบ + แบ่งปัน + อนุญาต + จำเป็นต้องเปิดใช้งาน Bluetooth + เปิดใช้งาน + ต้องมีการอนุญาตใหม่ + แตะเพื่อให้สิทธิ์ที่จำเป็นในการแจ้งเตือนการสัมผัส + เปิดการตั้งค่าตำแหน่ง + ไม่มีการรายงานการเผชิญเหตุการสัมผัส + การแจ้งเตือนการสัมผัสต้องได้รับอนุญาตเพิ่มเติมจึงจะทำงานได้ + %1$d รหัสประจำตัว ในชั่วโมงที่ผ่านมา + %1$s คะแนนความเสี่ยง %2$d + หมายเหตุ: คะแนนความเสี่ยงถูกกำหนดโดยแอป ตัวเลขสูงอาจหมายถึงความเสี่ยงต่ำหรือในทางกลับกัน + %1$d รวบรวม รหัสประจำตัวแล้ว + ลบ + ไม่มีข้อมูลบันทึกไว้ + การลบ รหัสประจำตัว ที่เก็บรวบรวมไว้จะทำให้ไม่สามารถแจ้งให้คุณทราบได้ในกรณีที่ผู้ติดต่อของคุณในช่วง 14 วันล่าสุดได้รับการวินิจฉัย + API การแจ้งเตือนการสัมผัสช่วยให้แอปสามารถแจ้งให้คุณทราบหากคุณสัมผัสกับบุคคลที่รายงานว่าได้รับการวินิจฉัยว่าติดเชื้อ\n\nวันที่ ระยะเวลา และความแรงของสัญญาณที่เกี่ยวข้องกับการสัมผัสจะถูกแชร์กับแอปที่เกี่ยวข้อง + ขณะที่เปิดใช้งาน API “การแจ้งเตือนการสัมผัส” อุปกรณ์ของคุณจะทำการรวบรวม รหัสประจำตัว (เรียกว่า Rolling Proximity Identifiers หรือ RPI) จากอุปกรณ์ใกล้เคียงโดยอัตโนมัติ\n\nเมื่อเจ้าของอุปกรณ์รายงานว่าได้รับการวินิจฉัยว่าเป็นบวก รหัสประจำตัว ของพวกเขาจะถูกแชร์ได้ อุปกรณ์ของคุณจะทำการตรวจสอบว่ามี รหัสประจำตัว ที่ได้รับการวินิจฉัยที่ทราบแล้วตรงกับ รหัสประจำตัว ที่รวบรวมไว้หรือไม่ และคำนวณความเสี่ยงต่อการติดเชื้อของคุณ + โทรศัพท์ของคุณต้องใช้บลูทูธเพื่อรวบรวมและแบ่งปัน ID กับโทรศัพท์เครื่องอื่นที่อยู่ใกล้เคียงอย่างปลอดภัย %1$s สามารถแจ้งเตือนคุณได้หากคุณสัมผัสกับบุคคลที่รายงานว่าได้รับการวินิจฉัยว่าติดเชื้อ วันที่ ระยะเวลา และความแรงของสัญญาณที่เกี่ยวข้องกับการสัมผัสจะถูกแชร์กับแอป + อัปเดตการตั้งค่า + %1$s ต้องได้รับการอนุญาตเพิ่มเติม + จำเป็นต้องมีการเข้าถึงตำแหน่ง + เกือบเสร็จแล้ว! คุณจะต้องเปิดใช้งานการเข้าถึงตำแหน่งพื้นหลังโดยเลือกตัวเลือก \"อนุญาตตลอดเวลา\" ในหน้าจอถัดไป จากนั้นกดย้อนกลับ + \ No newline at end of file diff --git a/play-services-nearby/core/src/main/res/values-tr/strings.xml b/play-services-nearby/core/src/main/res/values-tr/strings.xml index a6b3daec93..3ba5cb67d7 100644 --- a/play-services-nearby/core/src/main/res/values-tr/strings.xml +++ b/play-services-nearby/core/src/main/res/values-tr/strings.xml @@ -1,2 +1,62 @@ - \ No newline at end of file + + Bluetooth\'u etkinleştir + Temas Bildirimleri devre dışı + Temas Bildirimleri\'ni almak için Bluetooth\'un etkinleştirilmesi gerekiyor. + Temas Bildirimleri\'ni almak için konum izinleri gerekiyor. + Temas Bildirimleri\'ni almak için hem Bluetooth hem de konum izinlerinin etkinleştirilmesi gerekiyor. + Temas Bildirimleri\'ni etkinleştirmek için, bu özelliği kullanan bir uygulamayı açın. + Konum ayarlarını aç + Temas Bildirimleri kullanan uygulamalar + Toplanan kimlikler + Ne yazık ki, cihazınız Temas Bildirimleri\'ni sadece kısmen destekliyor. Bu da, kendi riskli temaslarınızdan bilgilendirileceğiniz ancak başkalarını bilgilendiremeyeceğiniz anlamına geliyor. + Şu anki yayınlanan kimlik + 5 dakikadan az + yakın temas + Rapor edilen temaslar + Güncellendi: %1$s + %1$s, %2$s + %1$d teşhis anahtarı işlendi. + Son 14 gün içinde API kullanımı + Kayıt yok + %1$d kimlik toplandı + %1$d temas karşılaşmaları raporlandı: + %1$s, risk skoru %2$d + Sil + Toplanan tüm kimlikleri sil + Yine de sil + Saat başı %1$s kimlik + Toplanan kimliklerin silinmesi, son 14 gün içindeki kişilerinizden herhangi birinin teşhis edilmesi durumunda bilgilendirilmenizi imkansız hale getirecektir. + Veriler ile analiz yapmak için kullanmak üzere toplanan kimlikleri başka bir uygulama ile dışa aktarın. + Temas Bildirimleri API\'si, uygulamaların, daha önceden pozitif teşhis olduğu bildirilen kişilerle temas halinde olmanız durumunda sizi bilgilendirmesine olanak tanır.\n\nBulunduğunuz temasın, tarihi, süresi, ve sinyal menzili, ilgili uygulama ile paylaşılır. + Temas Bildirimleri\'ni etkinleştir? + Temas Bildirimleri\'ni kullan + Kimlikleri güvenli bir şekilde toplamak ve yakındaki diğer cihazlarla paylaşmak için cihazınızın Bluetooth\'u kullanması gerekir. %1$s, pozitif teşhis konulduğunu bildiren biri ile temas halinde kalırsanız sizi bilgilendirebilir. Temas ile ilişkili tarih, süre ve sinyal gücü uygulama ile paylaşılacaktır. + Etkinleştir + Temas Bildirimleri\'ni devre dışı bırak? + Temas Bildirimleri\'ni devre dışı bırakmanız halinde, pozitif teşhis konulduğu bilinen biri ile temasa geçmeniz durumunda bilgilendirilmeyeceksiniz. + Kimliklerinizi %1$s ile paylaşmak istiyor musunuz? + İzin ver + Konum erişimi gerekiyor. + Yeni izinler gerekiyor + Temas Bildirimleri\'ne gerekli izinleri vermek için dokunun + Ayarları değiştir + %1$s ek izinlere ihtiyaç duyuyor. + Son saat içinde %1$d kimlik + Dışa aktar + Etkinleştir + Devre dışı bırak + Bluetooth\'un etkinleştirilmesi gerekiyor. + Ne yazık ki, cihazınız Temas Bildirimleri için uyumlu değil. + Paylaş + Temas Bildirimleri + uzak temas + Bildirilen bir temas yok. + Temas Bildirimleri\'nin çalışması için ek izinlere ihtiyaç var + Yaklaşık %1$d dakika + Not: Risk skoru uygulama tarafından belirlenir. Yüksek sayılar düşük risk anlamına veya tam tersini olabilir. + Temas Bildirimleri API\'si etkin iken, cihazınız yakındaki diğer cihazlardan pasif olarak kimlik toplar. (bu kimlikler, Rolling Identifiers, veya RPI olarak da bilinir)\n\nCihaz sahiplerin testi pozitif çıktığı bilindiğinde, kendilerinin kimlikleri paylaşılabilir. Bu sayede, cihazınız, bilinen kimlikler ile toplanan kimlikleri eşleştirir ve enfeksiyon riskinizi hesaplar. + Neredeyse bitti! Bir sonraki ekranda, arkaplanda konum erişimini vermek için \'Her zaman izin ver\' seçeneğini seçin. Ardından geri dönün. + %2$s için %1$d çağrı + Son 14 gün içindeki kimlikleriniz, çevrenizdekilere potansiyel temasta bulunduğunuz konusunda haber vermeye yardımcı olmak için kullanılır.\n\nKişisel bilgileriniz veya test sonuçlarınız başkaları ile paylaşılmaz. + \ No newline at end of file diff --git a/play-services-nearby/core/src/main/res/values-uk/strings.xml b/play-services-nearby/core/src/main/res/values-uk/strings.xml index 9b3c80efa7..5aab04f1f7 100644 --- a/play-services-nearby/core/src/main/res/values-uk/strings.xml +++ b/play-services-nearby/core/src/main/res/values-uk/strings.xml @@ -52,7 +52,7 @@ \nДата, тривалість і рівень сигналу, пов\'язані з контактом, будуть передані відповідному застосунку. Використовувати сповіщення про вплив Увімкнути сповіщення про вплив? - Ваш телефон повинен використовувати Bluetooth для безпечного збору та обміну ідентифікаторами з іншими телефонами, які знаходяться поблизу. \u0020%1$s може сповістити вас, якщо ви зазнали контакту з людиною, яка повідомила про позитивний діагноз. \u0020Дата, тривалість і рівень сигналу, пов\'язані з контактом, будуть передані застосунку. + Ваш телефон повинен використовувати Bluetooth для безпечного збору та обміну ідентифікаторами з іншими телефонами, які знаходяться поблизу. %1$s може сповістити вас, якщо ви зазнали контакту з людиною, яка повідомила про позитивний діагноз. Дата, тривалість і рівень сигналу, пов\'язані з контактом, будуть передані застосунку. Поділітися своїми ідентифікаторами з %1$s\? Ваші ідентифікаційні дані за останні 14 днів будуть використані, щоби повідомити інших людей, які перебували поруч з вами, про потенційний ризик зараження. \n diff --git a/play-services-nearby/core/src/main/res/values-vi/strings.xml b/play-services-nearby/core/src/main/res/values-vi/strings.xml index a6b3daec93..c6c788491e 100644 --- a/play-services-nearby/core/src/main/res/values-vi/strings.xml +++ b/play-services-nearby/core/src/main/res/values-vi/strings.xml @@ -1,2 +1,62 @@ - \ No newline at end of file + + Thông báo Tiếp xúc đang không hoạt động + Cần bật Bluetooth để nhận Thông báo Tiếp xúc. + Cần quyền truy cập Vị trí để nhận Thông báo Tiếp xúc. + Thông báo Tiếp xúc cần thêm các quyền để hoạt động + Thông báo Tiếp xúc + Mở cài đặt Vị trí + Rất tiếc, thiết bị của bạn không tương thích với Thông báo Tiếp xúc. + Cần bật Bluetooth và quyền truy cập Vị trí để nhận Thông báo Tiếp xúc. + Ứng dụng sử dụng Thông báo Tiếp xúc + Các ID đã thu thập + ID đang được phát sóng + Các tiếp xúc đã được báo cáo + Ít hơn 5 phút + Đã cập nhật: %1$s + Khoảng %1$d phút + tiếp xúc gần + tiếp xúc xa + %1$s, %2$s + Đã xử trí khoá chẩn đoán %1$d. + Không có trường hợp tiếp xúc nào được báo cáo. + Lưu ý: Điểm rủi ro được xác định bởi ứng dụng. Số điểm cao có thể liên quan đến rủi ro thấp hoặc ngược lại. + Sử dụng API trong 14 ngày qua + %1$d gọi đến %2$s + Đã báo cáo các trường hợp tiếp xúc với %1$d: + %1$s, điểm rủi ro %2$d + %1$d ID đã thu thập + %1$s ID mỗi giờ + Xoá + Xoá tất cả ID đã thu thập + Không có bản ghi + Việc xóa các ID đã thu thập sẽ khiến bạn không thể nhận thông báo nếu bất kỳ ai trong số các liên lạc của bạn trong 14 ngày qua được chẩn đoán. + Vẫn xoá + Xuất + Xuất ID đã thu thập để phân tích mở rộng bằng ứng dụng khác. + Khi đã kích hoạt API Thông báo Tiếp xúc, thiết bị của bạn sẽ thụ động thu thập các ID (gọi là Rolling Proximity Identifiers, hoặc RPIs) từ các thiết bị gần đó.\n\nKhi chủ sở hữu thiết được chẩn đoán dương tính, ID của họ có thể được chia sẻ. Thiết bị của bạn kiểm tra xem có ID nào đã biết được chẩn đoán dương tính trùng khớp với các ID đã thu thập hay không và tính toán mức độ rủi ro nhiễm bệnh của bạn. + Sử dụng Thông báo Tiếp xúc + Bật Thông báo Tiếp xúc? + Bật + Tắt Thông báo Tiếp xúc? + Sau khi tắt Thông báo Tiếp xúc, bạn sẽ không còn nhận được thông báo khi bạn đã tiếp xúc với ai đó đã được chẩn đoán dương tính. + Tắt + Chia sẻ ID của bạn với %1$s\? + ID của bạn trong 14 ngày qua sẽ được sử dụng để giúp thông báo cho người khác rằng bạn đã ở gần những trường hợp phơi nhiễm tiềm tàng.\n\nDanh tính hoặc kết quả xét nghiệm của bạn sẽ không được chia sẻ với người khác. + Chia sẻ + %1$s cần thêm quyền. + Cấp quyền + Cần phải bật Bluetooth. + Cần phải có quyền truy cập vị trí. + Bật + Nhấn để cấp các quyền cần thiết cho Thông báo Tiếp xúc + Gần xong rồi! Bạn sẽ cần bật quyền truy cập vị trí nền bằng cách chọn tùy chọn \'Cho phép mọi lúc\' trên màn hình tiếp theo. Sau đó nhấn quay lại. + Cập nhật cài đặt + Để bật Thông báo Tiếp xúc, hãy mở bất kỳ ứng dụng nào hỗ trợ tính năng này. + Bật Bluetooth + Rất tiếc, thiết bị của bạn chỉ tương thích một phần với Thông báo Tiếp xúc. Bạn có thể nhận thông báo về các tiếp xúc có nguy cơ, nhưng sẽ không thể thông báo cho người khác. + %1$d ID trong giờ qua + API Thông báo Tiếp xúc cho phép các ứng dụng thông báo cho bạn nếu bạn đã tiếp xúc với ai đó được chẩn đoán là dương tính.\n\nNgày, thời gian, và cường độ tín hiệu liên quan đến lần tiếp xúc sẽ được chia sẻ với ứng dụng tương ứng. + Điện thoại của bạn cần sử dụng Bluetooth để thu thập và chia sẻ an toàn các ID với các điện thoại khác ở gần. %1$s có thể thông báo cho bạn nếu bạn đã tiếp xúc với ai đó được chẩn đoán dương tính. Ngày, thời gian và cường độ tín hiệu liên quan đến một lần tiếp xúc sẽ được chia sẻ với ứng dụng. + Cần có Quyền mới + \ No newline at end of file diff --git a/play-services-oss-licenses/src/main/res/values-fr/strings.xml b/play-services-oss-licenses/src/main/res/values-fr/strings.xml index a6b3daec93..99bb5a2215 100644 --- a/play-services-oss-licenses/src/main/res/values-fr/strings.xml +++ b/play-services-oss-licenses/src/main/res/values-fr/strings.xml @@ -1,2 +1,9 @@ - \ No newline at end of file + + Une erreur s\'est produite en récupérant la licence. + Cette appli n\'a pas de licences libres. + Licences libres + Info sur les licences en cours de chargement. + Liste des licences en cours de chargement. + Détails des licences pour les logiciels libres + \ No newline at end of file diff --git a/play-services-oss-licenses/src/main/res/values-th/strings.xml b/play-services-oss-licenses/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..1fd242f6ea --- /dev/null +++ b/play-services-oss-licenses/src/main/res/values-th/strings.xml @@ -0,0 +1,9 @@ + + + แอปนี้ไม่มีใบอนุญาตโอเพนซอร์สใดๆ + กำลังโหลดรายการใบอนุญาต + รายละเอียดใบอนุญาตสำหรับซอฟต์แวร์โอเพ่นซอร์ส + เกิดข้อผิดพลาดขณะดึงใบอนุญาต + ใบอนุญาตโอเพ่นซอร์ส + กำลังโหลดข้อมูลใบอนุญาต + \ No newline at end of file diff --git a/play-services-oss-licenses/src/main/res/values-tr/strings.xml b/play-services-oss-licenses/src/main/res/values-tr/strings.xml index a6b3daec93..416ea3a4c5 100644 --- a/play-services-oss-licenses/src/main/res/values-tr/strings.xml +++ b/play-services-oss-licenses/src/main/res/values-tr/strings.xml @@ -1,2 +1,9 @@ - \ No newline at end of file + + Lisansı yüklemeye çalışırken bir sorunla karşılaşıldı. + Bu uygulamanın herhangi bir açık kaynak lisansı yok. + Açık kaynak lisansları + Lisans listesi yükleniyor. + Açık kaynak yazılım için lisans bilgileri + Lisans bilgisi yükleniyor. + \ No newline at end of file diff --git a/play-services-tapandpay/core/src/main/kotlin/org/microg/gms/tapandpay/TapAndPayService.kt b/play-services-tapandpay/core/src/main/kotlin/org/microg/gms/tapandpay/TapAndPayService.kt index 965f9e3db7..b8858a6ab1 100644 --- a/play-services-tapandpay/core/src/main/kotlin/org/microg/gms/tapandpay/TapAndPayService.kt +++ b/play-services-tapandpay/core/src/main/kotlin/org/microg/gms/tapandpay/TapAndPayService.kt @@ -5,7 +5,6 @@ package org.microg.gms.tapandpay import android.os.Parcel -import android.os.RemoteException import android.util.Log import android.util.SparseArray import com.google.android.gms.common.Feature @@ -36,20 +35,38 @@ class TapAndPayService : BaseService(TAG, GmsService.TAP_AND_PAY) { features = arrayOf( Feature("tapandpay", 1), Feature("tapandpay_account_linking", 1), + Feature("tapandpay_add_service_listener", 1), Feature("tapandpay_block_payment_cards", 1), Feature("tapandpay_check_contactless_eligibility", 1), Feature("tapandpay_dismiss_quick_access_wallet", 1), + Feature("tapandpay_enable_secure_keyguard", 1), + Feature("tapandpay_felica_tos", 1), + Feature("tapandpay_get_active_wallet_infos", 1L), Feature("tapandpay_get_all_cards_for_account", 1), Feature("tapandpay_get_contactless_setup_configuration", 1), + Feature("tapandpay_get_environment", 1L), Feature("tapandpay_get_last_attestation_result", 1), - Feature("tapandpay_get_token_pan", 1), + Feature("tapandpay_get_stable_hardware_id", 1L), + Feature("tapandpay_get_token_details", 1L), + Feature("tapandpay_get_token_status", 1L), Feature("tapandpay_global_actions", 1), + Feature("tapandpay_has_eligible_tokenization_target", 1L), Feature("tapandpay_issuer_api", 2), Feature("tapandpay_perform_tokenization_operation", 1), Feature("tapandpay_push_tokenize", 1), + Feature("tapandpay_override_payment_network", 3L), + Feature("tapandpay_get_parental_consent_intent", 1L), + Feature("tapandpay_perform_secure_element_management_operation", 1L), + Feature("tapandpay_perform_tokenization_operation", 1L), Feature("tapandpay_push_tokenize_session", 6), + Feature("tapandpay_push_tokenize", 1L), Feature("tapandpay_quick_access_wallet", 1), + Feature("tapandpay_report_unlock", 1L), + Feature("tapandpay_request_delete_token", 1L), + Feature("tapandpay_request_select_token", 1L), Feature("tapandpay_secureelement", 1), + Feature("tapandpay_settings", 2L), + Feature("tapandpay_token_listing_with_request", 1L), Feature("tapandpay_show_wear_card_management_view", 1), Feature("tapandpay_send_wear_request_to_phone", 1), Feature("tapandpay_sync_device_info", 1), @@ -58,6 +75,8 @@ class TapAndPayService : BaseService(TAG, GmsService.TAP_AND_PAY) { Feature("tapandpay_tokenize_pan", 1), Feature("tapandpay_transmission_event", 1), Feature("tapandpay_token_listing", 3), + Feature("tapandpay_wallet_ui_shown_status", 1L), + Feature("tapandpay_wallet_set_tap_doodle_enabled", 1L), Feature("tapandpay_wallet_feedback_psd", 1) ) }) @@ -101,6 +120,11 @@ class TapAndPayImpl : ITapAndPayService.Stub() { callbacks.onRefreshSeCardsResponse(Status.SUCCESS, RefreshSeCardsResponse()) } + override fun getListTokens(callbacks: ITapAndPayServiceCallbacks) { + Log.d(TAG, "getListTokensRequest: ") + callbacks.onListTokensRetrieved(Status.SUCCESS, emptyArray()) + } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayService.aidl b/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayService.aidl index 2468d490e8..7c8b1785a7 100644 --- a/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayService.aidl +++ b/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayService.aidl @@ -47,4 +47,5 @@ interface ITapAndPayService { void refreshSeCards(in RefreshSeCardsRequest request, ITapAndPayServiceCallbacks callbacks) = 56; // void tokenizeAccount(in TokenizeAccountRequest request, ITapAndPayServiceCallbacks callbacks) = 57; // void syncDeviceInfo(in SyncDeviceInfoRequest request, ITapAndPayServiceCallbacks callbacks) = 64; + void getListTokens(ITapAndPayServiceCallbacks callbacks) = 73; } diff --git a/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayServiceCallbacks.aidl b/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayServiceCallbacks.aidl index b23c21c08c..5bbd2aaf2a 100644 --- a/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayServiceCallbacks.aidl +++ b/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/internal/ITapAndPayServiceCallbacks.aidl @@ -5,6 +5,7 @@ import com.google.android.gms.tapandpay.firstparty.GetActiveAccountResponse; import com.google.android.gms.tapandpay.firstparty.GetAllCardsResponse; import com.google.android.gms.tapandpay.firstparty.RefreshSeCardsResponse; import com.google.android.gms.tapandpay.issuer.TokenStatus; +import com.google.android.gms.tapandpay.issuer.TokenInfo; interface ITapAndPayServiceCallbacks { void onTokenSelected(in Status status) = 1; @@ -50,7 +51,7 @@ interface ITapAndPayServiceCallbacks { // void onQuickAccessWalletConfig(in Status status, in QuickAccessWalletConfig config) = 46; // void onContactlessSetupStatusRetrieved(in Status status, in GetContactlessSetupStatusResponse response) = 47; void onIsTokenizedRetrieved(in Status status, boolean isTokenized) = 48; -// void onListTokensRetrieved(in Status status, in TokenInfo[] tokens) = 49; + void onListTokensRetrieved(in Status status, in TokenInfo[] tokens) = 49; // void onContactlessEligibilityRetrieved(in Status status, in CheckContactlessEligibilityResponse response) = 50; void onProto(in Status status, in byte[] proto) = 51; // void onPushProvisionSessionContextRetrieved(in Status status, in PushProvisionSessionContext context) = 52; diff --git a/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/issuer/TokenInfo.aidl b/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/issuer/TokenInfo.aidl new file mode 100644 index 0000000000..a5eb709410 --- /dev/null +++ b/play-services-tapandpay/src/main/aidl/com/google/android/gms/tapandpay/issuer/TokenInfo.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.tapandpay.issuer; + +parcelable TokenInfo; diff --git a/play-services-tapandpay/src/main/java/com/google/android/gms/tapandpay/issuer/TokenInfo.java b/play-services-tapandpay/src/main/java/com/google/android/gms/tapandpay/issuer/TokenInfo.java new file mode 100644 index 0000000000..d8e4e74e3c --- /dev/null +++ b/play-services-tapandpay/src/main/java/com/google/android/gms/tapandpay/issuer/TokenInfo.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.tapandpay.issuer; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class TokenInfo extends AbstractSafeParcelable { + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(TokenInfo.class); + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } +} diff --git a/vending-app/build.gradle b/vending-app/build.gradle index c8281eef76..7cc40124fe 100644 --- a/vending-app/build.gradle +++ b/vending-app/build.gradle @@ -113,6 +113,9 @@ dependencies { //droidguard implementation project(':play-services-droidguard') + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + //androidx implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.core:core-ktx:$coreVersion" diff --git a/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleService.aidl b/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleService.aidl index e70ef8e65e..d77f14d833 100644 --- a/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleService.aidl +++ b/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleService.aidl @@ -8,14 +8,14 @@ package com.google.android.play.core.assetpacks.protocol; import com.google.android.play.core.assetpacks.protocol.IAssetModuleServiceCallback; interface IAssetModuleService { - void startDownload(String packageName, in List list, in Bundle bundle, in IAssetModuleServiceCallback callback) = 1; - void getSessionStates(String packageName, in Bundle bundle, in IAssetModuleServiceCallback callback) = 4; - void notifyChunkTransferred(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 5; - void notifyModuleCompleted(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 6; - void notifySessionFailed(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 8; - void keepAlive(String packageName, in Bundle bundle, in IAssetModuleServiceCallback callback) = 9; - void getChunkFileDescriptor(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 10; - void requestDownloadInfo(String packageName, in List list, in Bundle bundle, in IAssetModuleServiceCallback callback) = 11; - void removeModule(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 12; - void cancelDownloads(String packageName, in List list, in Bundle bundle, in IAssetModuleServiceCallback callback) = 13; + oneway void startDownload(String packageName, in List list, in Bundle bundle, in IAssetModuleServiceCallback callback) = 1; + oneway void getSessionStates(String packageName, in Bundle bundle, in IAssetModuleServiceCallback callback) = 4; + oneway void notifyChunkTransferred(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 5; + oneway void notifyModuleCompleted(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 6; + oneway void notifySessionFailed(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 8; + oneway void keepAlive(String packageName, in Bundle bundle, in IAssetModuleServiceCallback callback) = 9; + oneway void getChunkFileDescriptor(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 10; + oneway void requestDownloadInfo(String packageName, in List list, in Bundle bundle, in IAssetModuleServiceCallback callback) = 11; + oneway void removeModule(String packageName, in Bundle bundle, in Bundle bundle2, in IAssetModuleServiceCallback callback) = 12; + oneway void cancelDownloads(String packageName, in List list, in Bundle bundle, in IAssetModuleServiceCallback callback) = 13; } \ No newline at end of file diff --git a/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleServiceCallback.aidl b/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleServiceCallback.aidl index 8e484fee36..25b47d92ef 100644 --- a/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleServiceCallback.aidl +++ b/vending-app/src/main/aidl/com/google/android/play/core/assetpacks/protocol/IAssetModuleServiceCallback.aidl @@ -6,17 +6,17 @@ package com.google.android.play.core.assetpacks.protocol; interface IAssetModuleServiceCallback { - void onStartDownload(int status, in Bundle bundle) = 1; - void onCancelDownload(int status) = 2; - void onGetSession(int status) = 3; - void onGetSessionStates(in List list) = 4; - void onNotifyChunkTransferred(in Bundle bundle) = 5; - void onError(in Bundle bundle) = 6; - void onNotifyModuleCompleted(in Bundle bundle) = 7; - void onNotifySessionFailed(in Bundle bundle) = 9; - void onKeepAlive(in Bundle bundle, in Bundle bundle2) = 10; - void onGetChunkFileDescriptor(in Bundle bundle, in Bundle bundle2) = 11; - void onRequestDownloadInfo(in Bundle bundle, in Bundle bundle2) = 12; - void onRemoveModule() = 13; - void onCancelDownloads() = 14; + oneway void onStartDownload(int sessionId, in Bundle bundle) = 1; + oneway void onCancelDownload(int status, in Bundle bundle) = 2; + oneway void onGetSession(int status, in Bundle bundle) = 3; + oneway void onGetSessionStates(in List list) = 4; + oneway void onNotifyChunkTransferred(in Bundle bundle, in Bundle bundle2) = 5; + oneway void onError(in Bundle bundle) = 6; + oneway void onNotifyModuleCompleted(in Bundle bundle, in Bundle bundle2) = 7; + oneway void onNotifySessionFailed(in Bundle bundle) = 9; + oneway void onKeepAlive(in Bundle bundle, in Bundle bundle2) = 10; + oneway void onGetChunkFileDescriptor(in Bundle bundle, in Bundle bundle2) = 11; + oneway void onRequestDownloadInfo(in Bundle bundle, in Bundle bundle2) = 12; + oneway void onRemoveModule(in Bundle bundle, in Bundle bundle2) = 13; + oneway void onCancelDownloads(in Bundle bundle) = 14; } \ No newline at end of file diff --git a/vending-app/src/main/java/com/android/vending/VendingPreferences.kt b/vending-app/src/main/java/com/android/vending/VendingPreferences.kt index 4786bbb370..ddd17e07b6 100644 --- a/vending-app/src/main/java/com/android/vending/VendingPreferences.kt +++ b/vending-app/src/main/java/com/android/vending/VendingPreferences.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023, e Foundation + * SPDX-FileCopyrightText: 2024 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -32,4 +33,12 @@ object VendingPreferences { c.getInt(0) != 0 } } + + @JvmStatic + fun isAssetDeliveryEnabled(context: Context): Boolean { + val projection = arrayOf(SettingsContract.Vending.ASSET_DELIVERY) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getInt(0) != 0 + } + } } \ No newline at end of file diff --git a/vending-app/src/main/java/com/google/android/finsky/assetmoduleservice/AssetModuleService.java b/vending-app/src/main/java/com/google/android/finsky/assetmoduleservice/AssetModuleService.java deleted file mode 100644 index 19ce4f2021..0000000000 --- a/vending-app/src/main/java/com/google/android/finsky/assetmoduleservice/AssetModuleService.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.google.android.finsky.assetmoduleservice; - -import android.app.Service; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.google.android.play.core.assetpacks.protocol.IAssetModuleService; -import com.google.android.play.core.assetpacks.protocol.IAssetModuleServiceCallback; - -import java.util.ArrayList; -import java.util.List; - -public class AssetModuleService extends Service { - private static final String TAG = "AssetModuleService"; - - private final List requested = new ArrayList<>(); - - private final IAssetModuleService.Stub service = new IAssetModuleService.Stub() { - - @Override - public void startDownload(String packageName, List list, Bundle bundle, IAssetModuleServiceCallback callback) throws RemoteException { - Log.d(TAG, "Method (startDownload) called by packageName -> " + packageName); - Bundle result = new Bundle(); - result.putStringArrayList("pack_names", new ArrayList<>()); - callback.onStartDownload(-1, result); - } - - @Override - public void getSessionStates(String packageName, Bundle bundle, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (getSessionStates) called but not implement by packageName -> " + packageName); - } - - @Override - public void notifyChunkTransferred(String packageName, Bundle bundle, Bundle bundle2, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (notifyChunkTransferred) called but not implement by packageName -> " + packageName); - } - - @Override - public void notifyModuleCompleted(String packageName, Bundle bundle, Bundle bundle2, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (notifyModuleCompleted) called but not implement by packageName -> " + packageName); - } - - @Override - public void notifySessionFailed(String packageName, Bundle bundle, Bundle bundle2, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (notifySessionFailed) called but not implement by packageName -> " + packageName); - } - - @Override - public void keepAlive(String packageName, Bundle bundle, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (keepAlive) called but not implement by packageName -> " + packageName); - } - - @Override - public void getChunkFileDescriptor(String packageName, Bundle bundle, Bundle bundle2, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (getChunkFileDescriptor) called but not implement by packageName -> " + packageName); - } - - @Override - public void requestDownloadInfo(String packageName, List list, Bundle bundle, IAssetModuleServiceCallback callback) throws RemoteException { - Log.d(TAG, "Method (requestDownloadInfo) called by packageName -> " + packageName); - Bundle result = new Bundle(); - if (requested.contains(packageName)) { - result.putInt("error_code", -5); - callback.onError(result); - return; - } - requested.add(packageName); - result.putStringArrayList("pack_names", new ArrayList<>()); - callback.onRequestDownloadInfo(result, result); - } - - @Override - public void removeModule(String packageName, Bundle bundle, Bundle bundle2, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (removeModule) called but not implement by packageName -> " + packageName); - } - - @Override - public void cancelDownloads(String packageName, List list, Bundle bundle, IAssetModuleServiceCallback callback) { - Log.d(TAG, "Method (cancelDownloads) called but not implement by packageName -> " + packageName); - } - }; - - @Override - public IBinder onBind(Intent intent) { - Log.d(TAG, "onBind"); - return service.asBinder(); - } - - @Override - public boolean onUnbind(Intent intent) { - Log.d(TAG, "onUnbind"); - requested.clear(); - return super.onUnbind(intent); - } -} \ No newline at end of file diff --git a/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java b/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java index fd67908a0c..a59ad44f9f 100644 --- a/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java +++ b/vending-app/src/main/java/com/google/android/finsky/externalreferrer/GetInstallReferrerService.java @@ -18,9 +18,8 @@ public class GetInstallReferrerService extends Service { // https://developer.android.com/google/play/installreferrer/igetinstallreferrerservice @Override public Bundle getInstallReferrer(Bundle request) throws RemoteException { - String packageName = request.getString("package_name"); Bundle result = new Bundle(); - result.putString("install_referrer", "https://play.google.com/store/apps/details?utm_source=google-play&utm_medium=organic&id="+packageName); + result.putString("install_referrer", "utm_source=google-play&utm_medium=organic"); result.putLong("referrer_click_timestamp_seconds", 0); result.putLong("referrer_click_timestamp_server_seconds", 0); result.putLong("install_begin_timestamp_seconds", 0); diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetLocation.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetLocation.java new file mode 100644 index 0000000000..4ec647459b --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetLocation.java @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import com.google.android.play.core.assetpacks.model.AssetPackStorageMethod; + +/** + * Location of a single asset, belonging to an asset pack. + *

+ * If the AssetPackStorageMethod for the pack is {@link AssetPackStorageMethod#APK_ASSETS}, this will be the path to the + * APK containing the asset, the offset of the asset inside the APK and the size of the asset. The asset file will be + * uncompressed, unless `bundletool` has been explicitly configured to compress the asset pack. + *

+ * If the AssetPackStorageMethod for the pack is {@link AssetPackStorageMethod#STORAGE_FILES}, this will be the path to + * the specific asset, the offset will be 0 and the size will be the size of the asset file. The asset file will be + * uncompressed. + */ +public abstract class AssetLocation { + /** + * Returns the file offset where the asset starts, in bytes. + */ + public abstract long offset(); + + /** + * Returns the path to the file containing the asset. + */ + public abstract String path(); + + /** + * Returns the size of the asset, in bytes. + */ + public abstract long size(); +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackException.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackException.java new file mode 100644 index 0000000000..090c592fcb --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackException.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.common.api.Status; +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode; +import org.microg.gms.common.Hide; + +/** + * An exception indicating something went wrong with the Asset Delivery API. + *

+ * See {@link #getErrorCode()} for the specific problem. + */ +public class AssetPackException extends ApiException { + @Hide + public AssetPackException(@AssetPackErrorCode int errorCode) { + super(new Status(errorCode, "Asset Pack Download Error(" + errorCode + ")")); + } + + /** + * Returns an error code value from {@link AssetPackErrorCode}. + */ + @AssetPackErrorCode + public int getErrorCode() { + return super.getStatusCode(); + } + + /** + * Returns the error code resulting from the operation. The value is one of the constants in {@link AssetPackErrorCode}. + * getStatusCode() is unsupported by AssetPackException, please use getErrorCode() instead. + */ + @Override + public int getStatusCode() { + return super.getStatusCode(); + } +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackLocation.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackLocation.java new file mode 100644 index 0000000000..abf6e6e71d --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackLocation.java @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import androidx.annotation.Nullable; +import com.google.android.play.core.assetpacks.model.AssetPackStorageMethod; + +/** + * Location of an asset pack on the device. + */ +public abstract class AssetPackLocation { + /** + * Returns the file path to the folder containing the pack's assets, if the storage method is + * {@link AssetPackStorageMethod#STORAGE_FILES}. + *

+ * The files found at this path should not be modified. + *

+ * If the storage method is {@link AssetPackStorageMethod#APK_ASSETS}, this method will return {@code null}. To access assets + * from packs installed as APKs, use Asset Manager. + */ + @Nullable + public abstract String assetsPath(); + + /** + * Returns whether the pack is installed as an APK or extracted into a folder on the filesystem. + * + * @return a value from {@link AssetPackStorageMethod} + */ + @AssetPackStorageMethod + public abstract int packStorageMethod(); + + /** + * Returns the file path to the folder containing the extracted asset pack, if the storage method is + * {@link AssetPackStorageMethod#STORAGE_FILES}. + *

+ * The files found at this path should not be modified. + *

+ * If the storage method is {@link AssetPackStorageMethod#APK_ASSETS}, this method will return {@code null}. To access assets + * from packs installed as APKs, use Asset Manager. + */ + @Nullable + public abstract String path(); +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManager.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManager.java new file mode 100644 index 0000000000..c4c23ae1c3 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManager.java @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import android.app.Activity; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.IntentSenderRequest; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.tasks.Task; +import com.google.android.play.core.assetpacks.model.AssetPackStatus; + +import java.util.List; +import java.util.Map; + +/** + * Manages downloads of asset packs. + */ +public interface AssetPackManager { + + /** + * Requests to cancel the download of the specified asset packs. + *

+ * Note: Only active downloads can be canceled. + * + * @return The new state for all specified packs. + */ + AssetPackStates cancel(@NonNull List packNames); + + /** + * Unregisters all listeners previously added using {@link #registerListener}. + */ + void clearListeners(); + + /** + * Requests to download the specified asset packs. + *

+ * This method will fail if the app is not in the foreground. + * + * @return the state of all specified pack names + */ + Task fetch(List packNames); + + /** + * [advanced API] Returns the location of an asset in a pack, or {@code null} if the asset is not present in the given pack. + *

+ * You don't need to use this API for common use-cases: you can use the standard File API for accessing assets from + * asset packs that were extracted into the filesystem; and you can use Android's AssetManager API to access assets + * from packs that were installed as APKs. + *

+ * This API is useful for game engines that don't use Asset Manager and for developers that want a unified method to + * access assets, independently from the delivery mode. + */ + @Nullable + AssetLocation getAssetLocation(@NonNull String packName, @NonNull String assetPath); + + /** + * Returns the location of the specified asset pack on the device or {@code null} if this pack is not downloaded. + *

+ * The files found at this path should not be modified. + */ + @Nullable + AssetPackLocation getPackLocation(@NonNull String packName); + + /** + * Returns the location of all installed asset packs as a mapping from the asset pack name to an {@link AssetPackLocation}. + *

+ * The files found at these paths should not be modified. + */ + Map getPackLocations(); + + /** + * Requests download state or details for the specified asset packs. + *

+ * Do not use this method to determine whether an asset pack is downloaded. Instead use {@link #getPackLocation}. + */ + Task getPackStates(List packNames); + + /** + * Registers a listener that will be notified of changes to the state of pack downloads for this app. Listeners should be + * subsequently unregistered using {@link #unregisterListener}. + */ + void registerListener(@NonNull AssetPackStateUpdateListener listener); + + /** + * Deletes the specified asset pack from the internal storage of the app. + *

+ * Use this method to delete asset packs instead of deleting files manually. This ensures that the Asset Pack will not be + * re-downloaded during an app update. + *

+ * If the asset pack is currently being downloaded or installed, this method does not cancel the process. For this case, + * use {@link #cancel} instead. + * + * @return A task that will be successful only if files were successfully deleted. + */ + + Task removePack(@NonNull String packName); + + /** + * Shows a confirmation dialog to resume all pack downloads that are currently in the + * {@link AssetPackStatus#WAITING_FOR_WIFI} state. If the user accepts the dialog, packs are downloaded over cellular data. + *

+ * The status of an asset pack is set to {@link AssetPackStatus#WAITING_FOR_WIFI} if the user is currently not on a Wi-Fi + * connection and the asset pack is large or the user has set their download preference in the Play Store to only + * download apps over Wi-Fi. By showing this dialog, your app can ask the user if they accept downloading the asset + * pack over cellular data instead of waiting for Wi-Fi. + *

+ * The confirmation activity returns one of the following values: + *

    + *
  • {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted. + *
  • {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g. + * backpress). + *
+ * + * @param activityResultLauncher an activityResultLauncher to launch the confirmation dialog. + * @return whether the confirmation dialog has been started. + * @deprecated This API has been deprecated in favor of {@link #showConfirmationDialog(ActivityResultLauncher)}. + */ + @Deprecated + boolean showCellularDataConfirmation(@NonNull ActivityResultLauncher activityResultLauncher); + + /** + * Shows a confirmation dialog to resume all pack downloads that are currently in the + * {@link AssetPackStatus#WAITING_FOR_WIFI} state. If the user accepts the dialog, packs are downloaded over cellular data. + *

+ * The status of an asset pack is set to {@link AssetPackStatus#WAITING_FOR_WIFI} if the user is currently not on a Wi-Fi + * connection and the asset pack is large or the user has set their download preference in the Play Store to only + * download apps over Wi-Fi. By showing this dialog, your app can ask the user if they accept downloading the asset + * pack over cellular data instead of waiting for Wi-Fi. + * + * @param activity the activity on top of which the confirmation dialog is displayed. Use your current + * activity for this. + * @return A {@link Task} that completes once the dialog has been accepted, denied or closed. A successful task + * result contains one of the following values: + *

    + *
  • {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted. + *
  • {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g. + * backpress). + *
+ * @deprecated This API has been deprecated in favor of {@link #showConfirmationDialog(Activity)}. + */ + @Deprecated + Task showCellularDataConfirmation(@NonNull Activity activity); + + /** + * Shows a dialog that asks the user for consent to download packs that are currently in either the + * {@link AssetPackStatus#REQUIRES_USER_CONFIRMATION} state or the {@link AssetPackStatus#WAITING_FOR_WIFI} state. + *

+ * If the app has not been installed by Play, an update may be triggered to ensure that a valid version is installed. This + * will cause the app to restart and all asset requests to be cancelled. These assets should be requested again after the + * app restarts. + *

+ * The confirmation activity returns one of the following values: + *

    + *
  • {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted. + *
  • {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g. + * backpress). + *
+ * + * @param activityResultLauncher an activityResultLauncher to launch the confirmation dialog. + * @return whether the confirmation dialog has been started. + */ + boolean showConfirmationDialog(@NonNull ActivityResultLauncher activityResultLauncher); + + /** + * Shows a dialog that asks the user for consent to download packs that are currently in either the + * {@link AssetPackStatus#REQUIRES_USER_CONFIRMATION} state or the {@link AssetPackStatus#WAITING_FOR_WIFI} state. + *

+ * If the app has not been installed by Play, an update may be triggered to ensure that a valid version is installed. This + * will cause the app to restart and all asset requests to be cancelled. These assets should be requested again after the + * app restarts. + * + * @param activity the activity on top of which the confirmation dialog is displayed. Use your current + * activity for this. + * @return A {@link Task} that completes once the dialog has been accepted, denied or closed. A successful task + * result contains one of the following values: + *

    + *
  • {@link Activity#RESULT_OK Activity#RESULT_OK} if the user accepted. + *
  • {@link Activity#RESULT_CANCELED Activity#RESULT_CANCELED} if the user denied or the dialog has been closed in any other way (e.g. + * backpress). + *
+ */ + Task showConfirmationDialog(@NonNull Activity activity); + + /** + * Unregisters a listener previously added using {@link #registerListener}. + */ + void unregisterListener(@NonNull AssetPackStateUpdateListener listener); +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerFactory.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerFactory.java new file mode 100644 index 0000000000..272d90efa5 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerFactory.java @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import android.content.Context; +import androidx.annotation.NonNull; + +/** + * Creates instances of {@link AssetPackManager}. + */ +public final class AssetPackManagerFactory { + private AssetPackManagerFactory() { + } + + /** + * Creates an instance of {@link AssetPackManager}. + * + * @param applicationContext a fully initialized application context + */ + @NonNull + public static AssetPackManager getInstance(Context applicationContext) { + return new AssetPackManagerImpl(); + } +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerImpl.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerImpl.java new file mode 100644 index 0000000000..4ee68d8c9e --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackManagerImpl.java @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import android.app.Activity; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.IntentSenderRequest; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.gms.tasks.Task; +import com.google.android.play.core.assetpacks.model.AssetPackStatus; +import org.microg.gms.common.Hide; + +import java.util.List; +import java.util.Map; + +@Hide +public class AssetPackManagerImpl implements AssetPackManager { + @Override + public AssetPackStates cancel(@NonNull List packNames) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearListeners() { + } + + @Override + public Task fetch(List packNames) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public AssetLocation getAssetLocation(@NonNull String packName, @NonNull String assetPath) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public AssetPackLocation getPackLocation(@NonNull String packName) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getPackLocations() { + throw new UnsupportedOperationException(); + } + + @Override + public Task getPackStates(List packNames) { + throw new UnsupportedOperationException(); + } + + @Override + public void registerListener(@NonNull AssetPackStateUpdateListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public Task removePack(@NonNull String packName) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean showCellularDataConfirmation(@NonNull ActivityResultLauncher activityResultLauncher) { + throw new UnsupportedOperationException(); + } + + @Override + public Task showCellularDataConfirmation(@NonNull Activity activity) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean showConfirmationDialog(@NonNull ActivityResultLauncher activityResultLauncher) { + throw new UnsupportedOperationException(); + } + + @Override + public Task showConfirmationDialog(@NonNull Activity activity) { + throw new UnsupportedOperationException(); + } + + @Override + public void unregisterListener(@NonNull AssetPackStateUpdateListener listener) { + + } + + public @AssetPackStatus int getLocalStatus(String packName, int remoteStatus) { + throw new UnsupportedOperationException(); + } + + public int getTransferProgressPercentage(String packName) { + throw new UnsupportedOperationException(); + } + + public String getInstalledVersionTag(String packName) { + throw new UnsupportedOperationException(); + } +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackServiceClient.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackServiceClient.java new file mode 100644 index 0000000000..321e86ea8a --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackServiceClient.java @@ -0,0 +1,366 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import android.content.Context; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode; +import com.google.android.play.core.assetpacks.model.AssetPackStatus; +import com.google.android.play.core.assetpacks.protocol.*; +import org.microg.gms.common.Hide; + +import java.util.*; + +@Hide +public class AssetPackServiceClient { + private static final String TAG = "AssetPackServiceClient"; + private List> pendingCalls = new ArrayList<>(); + private Context context; + private AssetPackManagerImpl assetPackManager; + + private interface PendingCall { + void execute(IAssetModuleService service, TaskCompletionSource completionSource) throws Exception; + } + + private Task execute(PendingCall pendingCall) { + TaskCompletionSource completionSource = new TaskCompletionSource<>(); + pendingCalls.add(completionSource); + try { + pendingCall.execute(null, completionSource); + } catch (Exception e) { + completionSource.trySetException(e); + } + Task task = completionSource.getTask(); + task.addOnCompleteListener(ignored -> pendingCalls.remove(completionSource)); + return task; + } + + private Bundle getOptionsBundle() { + Bundle options = new Bundle(); + // TODO + BundleKeys.put(options, BundleKeys.PLAY_CORE_VERSION_CODE, 20202); + BundleKeys.put(options, BundleKeys.SUPPORTED_COMPRESSION_FORMATS, new ArrayList<>(Arrays.asList(CompressionFormat.UNSPECIFIED, CompressionFormat.BROTLI))); + BundleKeys.put(options, BundleKeys.SUPPORTED_PATCH_FORMATS, new ArrayList<>(Arrays.asList(PatchFormat.PATCH_GDIFF, PatchFormat.GZIPPED_GDIFF))); + return options; + } + + private ArrayList getModuleNameBundles(List packNames) { + ArrayList moduleNameBundles = new ArrayList<>(); + for (String packName : packNames) { + Bundle arg = new Bundle(); + BundleKeys.put(arg, BundleKeys.MODULE_NAME, packName); + moduleNameBundles.add(arg); + } + return moduleNameBundles; + } + + private Bundle getInstalledAssetModulesBundle(Map installedAssetModules) { + Bundle installedAssetModulesBundle = getOptionsBundle(); + ArrayList installedAssetModuleBundles = new ArrayList<>(); + for (String moduleName : installedAssetModules.keySet()) { + Bundle installedAssetModuleBundle = new Bundle(); + BundleKeys.put(installedAssetModuleBundle, BundleKeys.INSTALLED_ASSET_MODULE_NAME, moduleName); + BundleKeys.put(installedAssetModuleBundle, BundleKeys.INSTALLED_ASSET_MODULE_VERSION, installedAssetModules.get(moduleName)); + installedAssetModuleBundles.add(installedAssetModuleBundle); + } + BundleKeys.put(installedAssetModulesBundle, BundleKeys.INSTALLED_ASSET_MODULE, installedAssetModuleBundles); + return installedAssetModulesBundle; + } + + private Bundle getSessionIdentifierBundle(int sessionId) { + Bundle sessionIdentifierBundle = new Bundle(); + BundleKeys.put(sessionIdentifierBundle, BundleKeys.SESSION_ID, sessionId); + return sessionIdentifierBundle; + } + + private Bundle getModuleIdentifierBundle(int sessionId, String moduleName) { + Bundle moduleIdentifierBundle = getSessionIdentifierBundle(sessionId); + BundleKeys.put(moduleIdentifierBundle, BundleKeys.MODULE_NAME, moduleName); + return moduleIdentifierBundle; + } + + private Bundle getChunkIdentifierBundle(int sessionId, String moduleName, String sliceId, int chunkNumber) { + Bundle chunkIdentifierBundle = getModuleIdentifierBundle(sessionId, moduleName); + BundleKeys.put(chunkIdentifierBundle, BundleKeys.SLICE_ID, sliceId); + BundleKeys.put(chunkIdentifierBundle, BundleKeys.CHUNK_NUMBER, chunkNumber); + return chunkIdentifierBundle; + } + + public Task getChunkFileDescriptor(int sessionId, String moduleName, String sliceId, int chunkNumber) { + return execute((service, completionSource) -> { + service.getChunkFileDescriptor(context.getPackageName(), getChunkIdentifierBundle(sessionId, moduleName, sliceId, chunkNumber), getOptionsBundle(), new BaseCallback(completionSource) { + @Override + public void onGetChunkFileDescriptor(ParcelFileDescriptor chunkFileDescriptor) { + completionSource.trySetResult(chunkFileDescriptor); + } + }); + }); + } + + public Task getPackStates(List packNames, Map installedAssetModules) { + return execute((service, completionSource) -> { + service.requestDownloadInfo(context.getPackageName(), getModuleNameBundles(packNames), getInstalledAssetModulesBundle(installedAssetModules), new BaseCallback(completionSource) { + @Override + public void onRequestDownloadInfo(Bundle bundle, Bundle bundle2) { + completionSource.trySetResult(AssetPackStatesImpl.fromBundle(bundle, assetPackManager)); + } + }); + }); + } + + public Task startDownload(List packNames, Map installedAssetModules) { + Task task = execute((service, completionSource) -> { + service.startDownload(context.getPackageName(), getModuleNameBundles(packNames), getInstalledAssetModulesBundle(installedAssetModules), new BaseCallback(completionSource) { + @Override + public void onStartDownload(int status, Bundle bundle) { + completionSource.trySetResult(AssetPackStatesImpl.fromBundle(bundle, assetPackManager, true)); + } + }); + }); + task.addOnSuccessListener(ignored -> keepAlive()); + return task; + } + + public Task> syncPacks(Map installedAssetModules) { + return execute((service, completionSource) -> { + service.getSessionStates(context.getPackageName(), getInstalledAssetModulesBundle(installedAssetModules), new BaseCallback(completionSource) { + @Override + public void onGetSessionStates(List list) { + ArrayList packNames = new ArrayList<>(); + for (Bundle bundle : list) { + Collection packStates = AssetPackStatesImpl.fromBundle(bundle, assetPackManager, true).packStates().values(); + if (!packStates.isEmpty()) { + AssetPackState state = packStates.iterator().next(); + switch (state.status()) { + case AssetPackStatus.PENDING: + case AssetPackStatus.DOWNLOADING: + case AssetPackStatus.TRANSFERRING: + case AssetPackStatus.WAITING_FOR_WIFI: + case AssetPackStatus.REQUIRES_USER_CONFIRMATION: + packNames.add(state.name()); + } + } + } + completionSource.trySetResult(packNames); + } + }); + }); + } + + public void cancelDownloads(List packNames) { + execute((service, completionSource) -> { + service.cancelDownloads(context.getPackageName(), getModuleNameBundles(packNames), getOptionsBundle(), new BaseCallback(completionSource) { + @Override + public void onCancelDownloads() { + completionSource.trySetResult(null); + } + }); + }); + } + + public void keepAlive() { + // TODO + } + + public void notifyChunkTransferred(int sessionId, String moduleName, String sliceId, int chunkNumber) { + execute((service, completionSource) -> { + service.notifyChunkTransferred(context.getPackageName(), getChunkIdentifierBundle(sessionId, moduleName, sliceId, chunkNumber), getOptionsBundle(), new BaseCallback(completionSource) { + @Override + public void onNotifyChunkTransferred(int sessionId, String moduleName, String sliceId, int chunkNumber) { + completionSource.trySetResult(null); + } + }); + }); + } + + public void notifyModuleCompleted(int sessionId, String moduleName) { + notifyModuleCompleted(sessionId, moduleName, 10); + } + + public void notifyModuleCompleted(int sessionId, String moduleName, int maxRetries) { + execute((service, completionSource) -> { + service.notifyModuleCompleted(context.getPackageName(), getModuleIdentifierBundle(sessionId, moduleName), getOptionsBundle(), new BaseCallback(completionSource) { + @Override + public void onError(int errorCode) { + if (maxRetries > 0) { + notifyModuleCompleted(sessionId, moduleName, maxRetries - 1); + } + } + }); + }); + } + + public void notifySessionFailed(int sessionId) { + execute((service, completionSource) -> { + service.notifySessionFailed(context.getPackageName(), getSessionIdentifierBundle(sessionId), getOptionsBundle(), new BaseCallback(completionSource) { + @Override + public void onNotifySessionFailed(int sessionId) { + completionSource.trySetResult(null); + } + }); + }); + } + + public void removePack(String packName) { + execute((service, completionSource) -> { + service.removeModule(context.getPackageName(), getModuleIdentifierBundle(0, packName), getOptionsBundle(), new BaseCallback(completionSource) { + @Override + public void onRemoveModule() { + completionSource.trySetResult(null); + } + }); + }); + } + + private static class BaseCallback extends IAssetModuleServiceCallback.Stub { + @NonNull + private final TaskCompletionSource completionSource; + + public BaseCallback(@NonNull TaskCompletionSource completionSource) { + this.completionSource = completionSource; + } + + @Override + public void onStartDownload(int sessionId, Bundle bundle) { + Log.i(TAG, "onStartDownload(" + sessionId + ")"); + onStartDownload(sessionId); + } + + public void onStartDownload(int sessionId) { + completionSource.trySetException(new Exception("Unexpected callback: onStartDownload")); + } + + @Override + public void onCancelDownload(int status, Bundle bundle) { + Log.i(TAG, "onCancelDownload(" + status + ")"); + onCancelDownload(status); + } + + public void onCancelDownload(int status) { + completionSource.trySetException(new Exception("Unexpected callback: onCancelDownload")); + } + + @Override + public void onGetSession(int status, Bundle bundle) { + Log.i(TAG, "onGetSession(" + status + ")"); + onGetSession(status); + } + + public void onGetSession(int status) { + completionSource.trySetException(new Exception("Unexpected callback: onGetSession")); + } + + @Override + public void onGetSessionStates(List list) { + completionSource.trySetException(new Exception("Unexpected callback: onGetSessionStates")); + } + + @Override + public void onNotifyChunkTransferred(Bundle bundle, Bundle bundle2) { + int sessionId = BundleKeys.get(bundle, BundleKeys.SESSION_ID, 0); + String moduleName = BundleKeys.get(bundle, BundleKeys.MODULE_NAME); + String sliceId = BundleKeys.get(bundle, BundleKeys.SLICE_ID); + int chunkNumber = BundleKeys.get(bundle, BundleKeys.CHUNK_NUMBER, 0); + Log.i(TAG, "onNotifyChunkTransferred(" + sessionId + ", " + moduleName + ", " + sliceId + ", " + chunkNumber + ")"); + onNotifyChunkTransferred(sessionId, moduleName, sliceId, chunkNumber); + } + + public void onNotifyChunkTransferred(int sessionId, String moduleName, String sliceId, int chunkNumber) { + completionSource.trySetException(new Exception("Unexpected callback: onNotifyChunkTransferred")); + } + + @Override + public void onError(Bundle bundle) { + int errorCode = BundleKeys.get(bundle, BundleKeys.ERROR_CODE, AssetPackErrorCode.INTERNAL_ERROR); + onError(errorCode); + } + + public void onError(int errorCode) { + completionSource.trySetException(new AssetPackException(errorCode)); + } + + @Override + public void onNotifyModuleCompleted(Bundle bundle, Bundle bundle2) { + int sessionId = BundleKeys.get(bundle, BundleKeys.SESSION_ID, 0); + String moduleName = BundleKeys.get(bundle, BundleKeys.MODULE_NAME); + Log.i(TAG, "onNotifyModuleCompleted(" + sessionId + ", " + moduleName + ")"); + onNotifyModuleCompleted(sessionId, moduleName); + } + + public void onNotifyModuleCompleted(int sessionId, String moduleName) { + completionSource.trySetException(new Exception("Unexpected callback: onNotifyModuleCompleted")); + } + + @Override + public void onNotifySessionFailed(Bundle bundle) { + int sessionId = BundleKeys.get(bundle, BundleKeys.SESSION_ID, 0); + Log.i(TAG, "onNotifySessionFailed(" + sessionId + ")"); + onNotifySessionFailed(sessionId); + } + + public void onNotifySessionFailed(int sessionId) { + completionSource.trySetException(new Exception("Unexpected callback: onNotifySessionFailed")); + } + + @Override + public void onKeepAlive(Bundle bundle, Bundle bundle2) { + boolean keepAlive = BundleKeys.get(bundle, BundleKeys.KEEP_ALIVE, false); + Log.i(TAG, "onKeepAlive(" + keepAlive + ")"); + onKeepAlive(keepAlive); + } + + public void onKeepAlive(boolean keepAlive) { + completionSource.trySetException(new Exception("Unexpected callback: onKeepAlive")); + } + + @Override + public void onGetChunkFileDescriptor(Bundle bundle, Bundle bundle2) { + ParcelFileDescriptor chunkFileDescriptor = BundleKeys.get(bundle, BundleKeys.CHUNK_FILE_DESCRIPTOR); + Log.i(TAG, "onGetChunkFileDescriptor(...)"); + onGetChunkFileDescriptor(chunkFileDescriptor); + } + + public void onGetChunkFileDescriptor(ParcelFileDescriptor chunkFileDescriptor) { + completionSource.trySetException(new Exception("Unexpected callback: onGetChunkFileDescriptor")); + } + + @Override + public void onRequestDownloadInfo(Bundle bundle, Bundle bundle2) { + Log.i(TAG, "onRequestDownloadInfo()"); + onRequestDownloadInfo(); + } + + public void onRequestDownloadInfo() { + completionSource.trySetException(new Exception("Unexpected callback: onRequestDownloadInfo")); + } + + @Override + public void onRemoveModule(Bundle bundle, Bundle bundle2) { + Log.i(TAG, "onRemoveModule()"); + onRemoveModule(); + } + + public void onRemoveModule() { + completionSource.trySetException(new Exception("Unexpected callback: onRemoveModule")); + } + + @Override + public void onCancelDownloads(Bundle bundle) { + Log.i(TAG, "onCancelDownload()"); + onCancelDownloads(); + } + + public void onCancelDownloads() { + completionSource.trySetException(new Exception("Unexpected callback: onCancelDownloads")); + } + } +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackState.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackState.java new file mode 100644 index 0000000000..c7028e9666 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackState.java @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode; +import com.google.android.play.core.assetpacks.model.AssetPackStatus; +import com.google.android.play.core.assetpacks.model.AssetPackUpdateAvailability; + +/** + * The state of an individual asset pack. + */ +public abstract class AssetPackState { + public abstract String availableVersionTag(); + + /** + * Returns the total number of bytes already downloaded for the pack. + */ + public abstract long bytesDownloaded(); + + /** + * Returns the error code for the pack, if Play has failed to download the pack. Returns + * {@link AssetPackErrorCode#NO_ERROR} if the download was successful or is in progress or has not been attempted. + * + * @return A value from {@link AssetPackErrorCode}. + */ + @AssetPackErrorCode + public abstract int errorCode(); + + public abstract String installedVersionTag(); + + /** + * Returns the name of the pack. + */ + public abstract String name(); + + /** + * Returns the download status of the pack. + *

+ * If the pack has never been requested before its status is {@link AssetPackStatus#UNKNOWN}. + * + * @return a value from {@link AssetPackStatus} + */ + @AssetPackStatus + public abstract int status(); + + /** + * Returns the total size of the pack in bytes. + */ + public abstract long totalBytesToDownload(); + + /** + * Returns the percentage of the asset pack already transferred to the app. + *

+ * This value is only defined when the status is {@link AssetPackStatus#TRANSFERRING}. + * + * @return a value between 0 and 100 inclusive. + */ + public abstract int transferProgressPercentage(); + + @AssetPackUpdateAvailability + public abstract int updateAvailability(); +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateImpl.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateImpl.java new file mode 100644 index 0000000000..18e1c81ee5 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateImpl.java @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import android.os.Bundle; +import androidx.annotation.NonNull; +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode; +import com.google.android.play.core.assetpacks.model.AssetPackStatus; +import com.google.android.play.core.assetpacks.model.AssetPackUpdateAvailability; +import com.google.android.play.core.assetpacks.protocol.BundleKeys; +import org.microg.gms.common.Hide; +import org.microg.gms.utils.ToStringHelper; + +@Hide +public class AssetPackStateImpl extends AssetPackState { + private final String name; + private final @AssetPackStatus int status; + private final @AssetPackErrorCode int errorCode; + private final long bytesDownloaded; + private final long totalBytesToDownload; + private final int transferProgressPercentage; + @AssetPackUpdateAvailability + private final int updateAvailability; + private final String availableVersionTag; + private final String installedVersionTag; + + public AssetPackStateImpl(String name, @AssetPackStatus int status, @AssetPackErrorCode int errorCode, long bytesDownloaded, long totalBytesToDownload, int transferProgressPercentage, @AssetPackUpdateAvailability int updateAvailability, String availableVersionTag, String installedVersionTag) { + this.name = name; + this.status = status; + this.errorCode = errorCode; + this.bytesDownloaded = bytesDownloaded; + this.totalBytesToDownload = totalBytesToDownload; + this.transferProgressPercentage = transferProgressPercentage; + this.updateAvailability = updateAvailability; + this.availableVersionTag = availableVersionTag; + this.installedVersionTag = installedVersionTag; + } + + @NonNull + public static AssetPackState fromBundle(Bundle bundle, @NonNull String name, AssetPackManagerImpl assetPackManager) { + return fromBundle(bundle, name, assetPackManager, false); + } + + @NonNull + public static AssetPackState fromBundle(Bundle bundle, @NonNull String name, AssetPackManagerImpl assetPackManager, boolean ignoreLocalStatus) { + @AssetPackStatus int remoteStatus = BundleKeys.get(bundle, BundleKeys.STATUS, name, 0); + @AssetPackStatus int status = ignoreLocalStatus ? remoteStatus : assetPackManager.getLocalStatus(name, remoteStatus); + @AssetPackErrorCode int errorCode = BundleKeys.get(bundle, BundleKeys.ERROR_CODE, name, 0); + long bytesDownloaded = BundleKeys.get(bundle, BundleKeys.BYTES_DOWNLOADED, name, 0L); + long totalBytesToDownload = BundleKeys.get(bundle, BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, name, 0L); + int transferProgressPercentage = assetPackManager.getTransferProgressPercentage(name); + long packVersion = BundleKeys.get(bundle, BundleKeys.PACK_VERSION, name, 0L); + long packBaseVersion = BundleKeys.get(bundle, BundleKeys.PACK_BASE_VERSION, name, 0L); + int appVersionCode = BundleKeys.get(bundle, BundleKeys.APP_VERSION_CODE, 0); + String availableVersionTag = BundleKeys.get(bundle, BundleKeys.PACK_VERSION_TAG, name, Integer.toString(appVersionCode)); + String installedVersionTag = assetPackManager.getInstalledVersionTag(name); + int updateAvailability = AssetPackUpdateAvailability.UPDATE_NOT_AVAILABLE; + if (status == AssetPackStatus.COMPLETED && packBaseVersion != 0 && packBaseVersion != packVersion) { + updateAvailability = AssetPackUpdateAvailability.UPDATE_AVAILABLE; + } + return new AssetPackStateImpl(name, status, errorCode, bytesDownloaded, totalBytesToDownload, transferProgressPercentage, updateAvailability, availableVersionTag, installedVersionTag); + } + + @Override + public String availableVersionTag() { + return availableVersionTag; + } + + @Override + public long bytesDownloaded() { + return bytesDownloaded; + } + + @Override + @AssetPackErrorCode + public int errorCode() { + return errorCode; + } + + @Override + public String installedVersionTag() { + return installedVersionTag; + } + + @Override + public String name() { + return name; + } + + @Override + @AssetPackStatus + public int status() { + return status; + } + + @Override + public long totalBytesToDownload() { + return totalBytesToDownload; + } + + @Override + public int transferProgressPercentage() { + return transferProgressPercentage; + } + + @Override + @AssetPackUpdateAvailability + public int updateAvailability() { + return updateAvailability; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("AssetPackState") + .field("name", name) + .field("status", status) + .field("errorCode", errorCode) + .field("bytesDownloaded", bytesDownloaded) + .field("totalBytesToDownload", totalBytesToDownload) + .field("transferProgressPercentage", transferProgressPercentage) + .field("updateAvailability", updateAvailability) + .field("availableVersionTag", availableVersionTag) + .field("installedVersionTag", installedVersionTag) + .end(); + } +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateUpdateListener.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateUpdateListener.java new file mode 100644 index 0000000000..1193f59aed --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStateUpdateListener.java @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import com.google.android.play.core.listener.StateUpdateListener; + +/** + * Listener that may be registered for updates on the state of the download of asset packs. + */ +public interface AssetPackStateUpdateListener extends StateUpdateListener { +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStates.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStates.java new file mode 100644 index 0000000000..2fc5332c17 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStates.java @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import java.util.Map; + +/** + * Contains the state for all requested packs. + */ +public abstract class AssetPackStates { + /** + * Returns a map from a pack's name to its state. + */ + public abstract Map packStates(); + + /** + * Returns total size of all requested packs in bytes. + */ + public abstract long totalBytes(); +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStatesImpl.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStatesImpl.java new file mode 100644 index 0000000000..445a1cc993 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/AssetPackStatesImpl.java @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks; + +import android.os.Bundle; +import androidx.annotation.NonNull; +import com.google.android.play.core.assetpacks.protocol.BundleKeys; +import org.microg.gms.common.Hide; +import org.microg.gms.utils.ToStringHelper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +@Hide +public class AssetPackStatesImpl extends AssetPackStates { + private final long totalBytes; + @NonNull + private final Map packStates; + + public AssetPackStatesImpl(long totalBytes, @NonNull Map packStates) { + this.totalBytes = totalBytes; + this.packStates = packStates; + } + + public static AssetPackStates fromBundle(@NonNull Bundle bundle, @NonNull AssetPackManagerImpl assetPackManager) { + return fromBundle(bundle, assetPackManager, false); + } + + @NonNull + public static AssetPackStates fromBundle(@NonNull Bundle bundle, @NonNull AssetPackManagerImpl assetPackManager, boolean ignoreLocalStatus) { + long totalBytes = BundleKeys.get(bundle, BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, 0L); + ArrayList packNames = BundleKeys.get(bundle, BundleKeys.PACK_NAMES); + Map packStates = new HashMap<>(); + if (packNames != null) { + for (String packName : packNames) { + packStates.put(packName, AssetPackStateImpl.fromBundle(bundle, packName, assetPackManager, ignoreLocalStatus)); + } + } + return new AssetPackStatesImpl(totalBytes, packStates); + } + + @Override + @NonNull + public Map packStates() { + return packStates; + } + + @Override + public long totalBytes() { + return totalBytes; + } + + @NonNull + @Override + public String toString() { + return ToStringHelper.name("AssetPackStates") + .field("totalBytes", totalBytes) + .field("packStates", packStates) + .end(); + } +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackErrorCode.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackErrorCode.java new file mode 100644 index 0000000000..65f8b294e9 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackErrorCode.java @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.model; + +import android.app.Activity; +import androidx.annotation.IntDef; +import com.google.android.play.core.assetpacks.AssetPackManager; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Error codes for the download of an asset pack. + */ +@Target({ElementType.TYPE_USE}) +@Retention(RetentionPolicy.CLASS) +@IntDef({AssetPackErrorCode.NO_ERROR, AssetPackErrorCode.APP_UNAVAILABLE, AssetPackErrorCode.PACK_UNAVAILABLE, AssetPackErrorCode.INVALID_REQUEST, AssetPackErrorCode.DOWNLOAD_NOT_FOUND, AssetPackErrorCode.API_NOT_AVAILABLE, AssetPackErrorCode.NETWORK_ERROR, AssetPackErrorCode.ACCESS_DENIED, AssetPackErrorCode.INSUFFICIENT_STORAGE, AssetPackErrorCode.APP_NOT_OWNED, AssetPackErrorCode.PLAY_STORE_NOT_FOUND, AssetPackErrorCode.NETWORK_UNRESTRICTED, AssetPackErrorCode.CONFIRMATION_NOT_REQUIRED, AssetPackErrorCode.UNRECOGNIZED_INSTALLATION, AssetPackErrorCode.INTERNAL_ERROR}) +public @interface AssetPackErrorCode { + int NO_ERROR = 0; + /** + * The requesting app is unavailable. + */ + int APP_UNAVAILABLE = -1; + /** + * The requested asset pack isn't available. + *

+ * This can happen if the asset pack wasn't included in the Android App Bundle that was published to the Play Store. + */ + int PACK_UNAVAILABLE = -2; + /** + * The request is invalid. + */ + int INVALID_REQUEST = -3; + /** + * The requested download isn't found. + */ + int DOWNLOAD_NOT_FOUND = -4; + /** + * The Asset Delivery API isn't available. + */ + int API_NOT_AVAILABLE = -5; + /** + * Network error. Unable to obtain the asset pack details. + */ + int NETWORK_ERROR = -6; + /** + * Download not permitted under the current device circumstances (e.g. in background). + */ + int ACCESS_DENIED = -7; + /** + * Asset pack download failed due to insufficient storage. + */ + int INSUFFICIENT_STORAGE = -10; + /** + * The Play Store app is either not installed or not the official version. + */ + int PLAY_STORE_NOT_FOUND = -11; + /** + * Returned if {@link AssetPackManager#showCellularDataConfirmation(Activity)} is called but no asset packs are + * waiting for Wi-Fi. + */ + int NETWORK_UNRESTRICTED = -12; + /** + * The app isn't owned by any user on this device. An app is "owned" if it has been installed via the Play Store. + */ + int APP_NOT_OWNED = -13; + /** + * Returned if {@link AssetPackManager#showConfirmationDialog(Activity)} is called but no asset packs require user + * confirmation. + */ + int CONFIRMATION_NOT_REQUIRED = -14; + /** + * The installed app version is not recognized by Play. This can happen if the app was not installed by Play. + */ + int UNRECOGNIZED_INSTALLATION = -15; + /** + * Unknown error downloading an asset pack. + */ + int INTERNAL_ERROR = -100; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStatus.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStatus.java new file mode 100644 index 0000000000..d6f7d2c174 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStatus.java @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.model; + +import android.app.Activity; +import androidx.annotation.IntDef; +import com.google.android.play.core.assetpacks.AssetPackManager; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Status of the download of an asset pack. + */ +@Target({ElementType.TYPE_USE}) +@Retention(RetentionPolicy.CLASS) +@IntDef({AssetPackStatus.UNKNOWN, AssetPackStatus.PENDING, AssetPackStatus.DOWNLOADING, AssetPackStatus.TRANSFERRING, AssetPackStatus.COMPLETED, AssetPackStatus.FAILED, AssetPackStatus.CANCELED, AssetPackStatus.WAITING_FOR_WIFI, AssetPackStatus.NOT_INSTALLED, AssetPackStatus.REQUIRES_USER_CONFIRMATION}) +public @interface AssetPackStatus { + /** + * The asset pack state is unknown. + */ + int UNKNOWN = 0; + /** + * The asset pack download is pending and will be processed soon. + */ + int PENDING = 1; + /** + * The asset pack download is in progress. + */ + int DOWNLOADING = 2; + /** + * The asset pack is being decompressed and copied (or patched) to the app's internal storage. + */ + int TRANSFERRING = 3; + /** + * The asset pack download and transfer is complete; the assets are available to the app. + */ + int COMPLETED = 4; + /** + * The asset pack download or transfer has failed. + */ + int FAILED = 5; + /** + * The asset pack download has been canceled by the user through the Play Store or the download notification. + */ + int CANCELED = 6; + /** + * The asset pack download is waiting for Wi-Fi to become available before proceeding. + *

+ * The app can ask the user to download a session that is waiting for Wi-Fi over cellular data by using + * {@link AssetPackManager#showCellularDataConfirmation(Activity)}. + */ + int WAITING_FOR_WIFI = 7; + /** + * The asset pack is not installed. + */ + int NOT_INSTALLED = 8; + /** + * The asset pack requires user consent to be downloaded. + *

+ * This can happen if the current app version was not installed by Play. + *

+ * If the asset pack is also waiting for Wi-Fi, this state takes precedence. + */ + int REQUIRES_USER_CONFIRMATION = 9; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStorageMethod.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStorageMethod.java new file mode 100644 index 0000000000..a3f94502db --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackStorageMethod.java @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.model; + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Method used to store an asset pack. + */ +@Target({ElementType.TYPE_USE}) +@Retention(RetentionPolicy.CLASS) +@IntDef({AssetPackStorageMethod.STORAGE_FILES, AssetPackStorageMethod.APK_ASSETS}) +public @interface AssetPackStorageMethod { + /** + * The asset pack is extracted into a folder containing individual asset files. + *

+ * Assets contained by this asset pack can be accessed via standard File APIs. + */ + int STORAGE_FILES = 0; + /** + * The asset pack is installed as APKs containing asset files. + *

+ * Assets contained by this asset pack can be accessed via Asset Manager. + */ + int APK_ASSETS = 1; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackUpdateAvailability.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackUpdateAvailability.java new file mode 100644 index 0000000000..faff47315f --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/model/AssetPackUpdateAvailability.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.model; + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE_USE}) +@Retention(RetentionPolicy.CLASS) +@IntDef({AssetPackUpdateAvailability.UNKNOWN, AssetPackUpdateAvailability.UPDATE_NOT_AVAILABLE, AssetPackUpdateAvailability.UPDATE_AVAILABLE}) +public @interface AssetPackUpdateAvailability { + int UNKNOWN = 0; + int UPDATE_NOT_AVAILABLE = 1; + int UPDATE_AVAILABLE = 2; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BroadcastConstants.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BroadcastConstants.java new file mode 100644 index 0000000000..399d566c73 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BroadcastConstants.java @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.protocol; + +import org.microg.gms.common.Hide; + +@Hide +public class BroadcastConstants { + public static String ACTION_SESSION_UPDATE = "com.google.android.play.core.assetpacks.receiver.ACTION_SESSION_UPDATE"; + public static String EXTRA_SESSION_STATE = "com.google.android.play.core.assetpacks.receiver.EXTRA_SESSION_STATE"; + public static String EXTRA_FLAGS = "com.google.android.play.core.FLAGS"; + public static String KEY_USING_EXTRACTOR_STREAM = "usingExtractorStream"; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BundleKeys.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BundleKeys.java new file mode 100644 index 0000000000..0abba2f3b8 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/BundleKeys.java @@ -0,0 +1,448 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.protocol; + +import android.content.Intent; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.BundleCompat; +import org.microg.gms.common.Hide; + +import java.util.ArrayList; + +@Hide +public final class BundleKeys { + public static RootKey APP_VERSION_CODE = new RootKey.Int("app_version_code"); + public static RootKey CHUNK_NUMBER = new RootKey.Int("chunk_number"); + public static RootKey CHUNK_FILE_DESCRIPTOR = new RootKey.Parcelable<>("chunk_file_descriptor", ParcelFileDescriptor.class); + public static RootKey KEEP_ALIVE = new RootKey.Bool("keep_alive"); + public static RootKey MODULE_NAME = new RootKey.String("module_name"); + public static RootKey SLICE_ID = new RootKey.String("slice_id"); + public static RootKey> PACK_NAMES = new RootKey.StringArrayList("pack_names"); + + // OptionsBundle + public static RootKey PLAY_CORE_VERSION_CODE = new RootKey.Int("playcore_version_code"); + public static RootKey> SUPPORTED_COMPRESSION_FORMATS = new RootKey.IntArrayList("supported_compression_formats"); + public static RootKey> SUPPORTED_PATCH_FORMATS = new RootKey.IntArrayList("supported_patch_formats"); + + // InstalledAssetModulesBundle + public static RootKey> INSTALLED_ASSET_MODULE = new RootKey.ParcelableArrayList<>("installed_asset_module", Bundle.class); + public static RootKey INSTALLED_ASSET_MODULE_NAME = new RootKey.String("installed_asset_module_name"); + public static RootKey INSTALLED_ASSET_MODULE_VERSION = new RootKey.Long("installed_asset_module_version"); + + public static RootAndPackKey SESSION_ID = new RootAndPackKey.Int("session_id"); + public static RootAndPackKey STATUS = new RootAndPackKey.Int("status"); + public static RootAndPackKey ERROR_CODE = new RootAndPackKey.Int("error_code"); + public static RootAndPackKey BYTES_DOWNLOADED = new RootAndPackKey.Long("bytes_downloaded"); + public static RootAndPackKey TOTAL_BYTES_TO_DOWNLOAD = new RootAndPackKey.Long("total_bytes_to_download"); + + public static PackKey PACK_VERSION = new PackKey.Long("pack_version"); + public static PackKey PACK_BASE_VERSION = new PackKey.Long("pack_base_version"); + public static PackKey PACK_VERSION_TAG = new PackKey.String("pack_version_tag"); + public static PackKey> SLICE_IDS = new PackKey.StringArrayList("slice_ids"); + + public static SliceKey> CHUNK_INTENTS = new SliceKey.ParcelableArrayList<>("chunk_intents", Intent.class); + public static SliceKey<@CompressionFormat Integer> COMPRESSION_FORMAT = new SliceKey.Int("compression_format"); + public static SliceKey<@PatchFormat Integer> PATCH_FORMAT = new SliceKey.Int("patch_format"); + public static SliceKey UNCOMPRESSED_HASH_SHA256 = new SliceKey.String("uncompressed_hash_sha256"); + public static SliceKey UNCOMPRESSED_SIZE = new SliceKey.Long("uncompressed_size"); + + private BundleKeys() { + } + + @Nullable + public static T get(Bundle bundle, @NonNull RootKey key) { + return key.get(bundle, key.baseKey()); + } + + public static T get(Bundle bundle, @NonNull RootKey key, T def) { + return key.get(bundle, key.baseKey(), def); + } + + public static void put(Bundle bundle, @NonNull RootKey key, T value) { + key.put(bundle, key.baseKey(), value); + } + + @Nullable + public static T get(Bundle bundle, @NonNull PackKey key, String packName) { + return key.get(bundle, packKey(packName, key.baseKey())); + } + + public static T get(Bundle bundle, @NonNull PackKey key, String packName, T def) { + return key.get(bundle, packKey(packName, key.baseKey()), def); + } + + public static void put(Bundle bundle, @NonNull PackKey key, String packName, T value) { + key.put(bundle, packKey(packName, key.baseKey()), value); + } + + @Nullable + public static T get(Bundle bundle, @NonNull SliceKey key, String packName, String sliceId) { + return key.get(bundle, sliceKey(packName, sliceId, key.baseKey())); + } + + public static T get(Bundle bundle, @NonNull SliceKey key, String packName, String sliceId, T def) { + return key.get(bundle, sliceKey(packName, sliceId, key.baseKey()), def); + } + + public static void put(Bundle bundle, @NonNull SliceKey key, String packName, String sliceId, T value) { + key.put(bundle, sliceKey(packName, sliceId, key.baseKey()), value); + } + + @NonNull + private static String packKey(String packName, String baseKey) { + return baseKey + ":" + packName; + } + + @NonNull + private static String sliceKey(String packName, String sliceId, String baseKey) { + return baseKey + ":" + packName + ":" + sliceId; + } + + public interface TypedBundleKey { + @NonNull + java.lang.String baseKey(); + + @Nullable + T get(@NonNull Bundle bundle, @NonNull java.lang.String key); + + T get(@NonNull Bundle bundle, @NonNull java.lang.String key, T def); + + void put(@NonNull Bundle bundle, @NonNull java.lang.String key, T value); + + abstract class Base implements TypedBundleKey { + @NonNull + public final java.lang.String baseKey; + + public Base(@NonNull java.lang.String baseKey) { + this.baseKey = baseKey; + } + + @NonNull + @Override + public java.lang.String baseKey() { + return baseKey; + } + } + + class Int extends Base { + + public Int(@NonNull java.lang.String key) { + super(key); + } + + @Override + public Integer get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return bundle.getInt(key); + } + + @Override + public Integer get(@NonNull Bundle bundle, @NonNull java.lang.String key, Integer def) { + return bundle.getInt(key, def); + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, Integer value) { + bundle.putInt(key, value); + } + } + + class Long extends Base { + + public Long(@NonNull java.lang.String key) { + super(key); + } + + @Override + public java.lang.Long get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return bundle.getLong(key); + } + + @Override + public java.lang.Long get(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.Long def) { + return bundle.getLong(key, def); + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.Long value) { + bundle.putLong(key, value); + } + } + + class Bool extends Base { + + public Bool(@NonNull java.lang.String key) { + super(key); + } + + @Override + public Boolean get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return bundle.getBoolean(key); + } + + @Override + public Boolean get(@NonNull Bundle bundle, @NonNull java.lang.String key, Boolean def) { + return bundle.getBoolean(key, def); + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, Boolean value) { + bundle.putBoolean(key, value); + } + } + + class String extends Base { + + public String(@NonNull java.lang.String key) { + super(key); + } + + @Override + public java.lang.String get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return bundle.getString(key); + } + + @Override + public java.lang.String get(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.String def) { + return bundle.getString(key, def); + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, java.lang.String value) { + bundle.putString(key, value); + } + } + + class Parcelable extends Base { + @NonNull + private final Class tClass; + + public Parcelable(@NonNull java.lang.String key, @NonNull Class tClass) { + super(key); + this.tClass = tClass; + } + + @Override + public T get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return BundleCompat.getParcelable(bundle, key, tClass); + } + + @Override + public T get(@NonNull Bundle bundle, @NonNull java.lang.String key, T def) { + if (bundle.containsKey(key)) { + return BundleCompat.getParcelable(bundle, key, tClass); + } else { + return def; + } + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, T value) { + bundle.putParcelable(key, value); + } + } + + class StringArrayList extends Base> { + public StringArrayList(@NonNull java.lang.String key) { + super(key); + } + + @Override + public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return bundle.getStringArrayList(key); + } + + @Override + public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList def) { + if (bundle.containsKey(key)) { + return bundle.getStringArrayList(key); + } else { + return def; + } + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList value) { + bundle.putStringArrayList(key, value); + } + } + + class IntArrayList extends Base> { + public IntArrayList(@NonNull java.lang.String key) { + super(key); + } + + @Override + public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return bundle.getIntegerArrayList(key); + } + + @Override + public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList def) { + if (bundle.containsKey(key)) { + return bundle.getIntegerArrayList(key); + } else { + return def; + } + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList value) { + bundle.putIntegerArrayList(key, value); + } + } + + class ParcelableArrayList extends Base> { + @NonNull + private final Class tClass; + + public ParcelableArrayList(@NonNull java.lang.String key, @NonNull Class tClass) { + super(key); + this.tClass = tClass; + } + + @Override + public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key) { + return BundleCompat.getParcelableArrayList(bundle, key, tClass); + } + + @Override + public ArrayList get(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList def) { + if (bundle.containsKey(key)) { + return BundleCompat.getParcelableArrayList(bundle, key, tClass); + } else { + return def; + } + } + + @Override + public void put(@NonNull Bundle bundle, @NonNull java.lang.String key, ArrayList value) { + bundle.putParcelableArrayList(key, value); + } + } + } + + + public interface PackKey extends TypedBundleKey { + class Int extends TypedBundleKey.Int implements PackKey { + public Int(@NonNull java.lang.String key) { + super(key); + } + } + + class Long extends TypedBundleKey.Long implements PackKey { + public Long(@NonNull java.lang.String key) { + super(key); + } + } + + class String extends TypedBundleKey.String implements PackKey { + public String(@NonNull java.lang.String key) { + super(key); + } + } + + class StringArrayList extends TypedBundleKey.StringArrayList implements PackKey> { + public StringArrayList(@NonNull java.lang.String key) { + super(key); + } + } + } + + public interface SliceKey extends TypedBundleKey { + class Int extends TypedBundleKey.Int implements SliceKey { + public Int(@NonNull java.lang.String key) { + super(key); + } + } + + class Long extends TypedBundleKey.Long implements SliceKey { + public Long(@NonNull java.lang.String key) { + super(key); + } + } + + class String extends TypedBundleKey.String implements SliceKey { + public String(@NonNull java.lang.String key) { + super(key); + } + } + + class ParcelableArrayList extends TypedBundleKey.ParcelableArrayList implements SliceKey> { + public ParcelableArrayList(@NonNull java.lang.String key, @NonNull Class tClass) { + super(key, tClass); + } + } + } + + public interface RootKey extends TypedBundleKey { + class Int extends TypedBundleKey.Int implements RootKey { + public Int(@NonNull java.lang.String key) { + super(key); + } + } + + class Long extends TypedBundleKey.Long implements RootKey { + public Long(@NonNull java.lang.String key) { + super(key); + } + } + + class Bool extends TypedBundleKey.Bool implements RootKey { + public Bool(@NonNull java.lang.String key) { + super(key); + } + } + + class String extends TypedBundleKey.String implements RootKey { + public String(@NonNull java.lang.String key) { + super(key); + } + } + + class Parcelable extends TypedBundleKey.Parcelable implements RootKey { + public Parcelable(@NonNull java.lang.String key, @NonNull Class tClass) { + super(key, tClass); + } + } + + class StringArrayList extends TypedBundleKey.StringArrayList implements RootKey> { + public StringArrayList(@NonNull java.lang.String key) { + super(key); + } + } + + class IntArrayList extends TypedBundleKey.IntArrayList implements RootKey> { + public IntArrayList(@NonNull java.lang.String key) { + super(key); + } + } + + class ParcelableArrayList extends TypedBundleKey.ParcelableArrayList implements RootKey> { + public ParcelableArrayList(@NonNull java.lang.String key, @NonNull Class tClass) { + super(key, tClass); + } + } + } + + public interface RootAndPackKey extends RootKey, PackKey { + + class Int extends TypedBundleKey.Int implements RootAndPackKey { + public Int(@NonNull java.lang.String key) { + super(key); + } + } + + class Long extends TypedBundleKey.Long implements RootAndPackKey { + public Long(@NonNull java.lang.String key) { + super(key); + } + } + } + +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/CompressionFormat.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/CompressionFormat.java new file mode 100644 index 0000000000..5b5ab46625 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/CompressionFormat.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.protocol; + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE_USE}) +@Retention(RetentionPolicy.CLASS) +@IntDef({CompressionFormat.UNSPECIFIED, CompressionFormat.BROTLI, CompressionFormat.GZIP, CompressionFormat.CHUNKED_GZIP, CompressionFormat.CHUNKED_BROTLI}) +public @interface CompressionFormat { + int UNSPECIFIED = 0; + int BROTLI = 1; + int GZIP = 2; + int CHUNKED_GZIP = 3; + int CHUNKED_BROTLI = 4; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/PatchFormat.java b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/PatchFormat.java new file mode 100644 index 0000000000..627c7c725a --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/assetpacks/protocol/PatchFormat.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.assetpacks.protocol; + + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE_USE}) +@Retention(RetentionPolicy.CLASS) +@IntDef({PatchFormat.UNKNOWN_PATCHING_FORMAT, PatchFormat.PATCH_GDIFF, PatchFormat.GZIPPED_GDIFF, PatchFormat.GZIPPED_BSDIFF, PatchFormat.GZIPPED_FILEBYFILE, PatchFormat.BROTLI_FILEBYFILE, PatchFormat.BROTLI_BSDIFF, PatchFormat.BROTLI_FILEBYFILE_RECURSIVE, PatchFormat.BROTLI_FILEBYFILE_ANDROID_AWARE, PatchFormat.BROTLI_FILEBYFILE_RECURSIVE_ANDROID_AWARE, PatchFormat.BROTLI_FILEBYFILE_ANDROID_AWARE_NO_RECOMPRESSION}) +public @interface PatchFormat { + int UNKNOWN_PATCHING_FORMAT = 0; + int PATCH_GDIFF = 1; + int GZIPPED_GDIFF = 2; + int GZIPPED_BSDIFF = 3; + int GZIPPED_FILEBYFILE = 4; + int BROTLI_FILEBYFILE = 5; + int BROTLI_BSDIFF = 6; + int BROTLI_FILEBYFILE_RECURSIVE = 7; + int BROTLI_FILEBYFILE_ANDROID_AWARE = 8; + int BROTLI_FILEBYFILE_RECURSIVE_ANDROID_AWARE = 9; + int BROTLI_FILEBYFILE_ANDROID_AWARE_NO_RECOMPRESSION = 10; +} diff --git a/vending-app/src/main/java/com/google/android/play/core/listener/StateUpdateListener.java b/vending-app/src/main/java/com/google/android/play/core/listener/StateUpdateListener.java new file mode 100644 index 0000000000..80433f2dc3 --- /dev/null +++ b/vending-app/src/main/java/com/google/android/play/core/listener/StateUpdateListener.java @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.play.core.listener; + +/** + * Base interface for state update listeners. + */ +public interface StateUpdateListener { + /** + * Callback triggered whenever the state has changed. + */ + void onStateUpdate(StateT state); +} diff --git a/vending-app/src/main/java/org/microg/vending/billing/Utils.kt b/vending-app/src/main/java/org/microg/vending/billing/Utils.kt index 831dd014cc..af89548c88 100644 --- a/vending-app/src/main/java/org/microg/vending/billing/Utils.kt +++ b/vending-app/src/main/java/org/microg/vending/billing/Utils.kt @@ -25,6 +25,7 @@ import androidx.core.app.ActivityCompat import androidx.core.os.bundleOf import com.android.billingclient.api.BillingClient.BillingResponseCode import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager import org.microg.gms.utils.digest import org.microg.gms.utils.getExtendedPackageInfo import org.microg.gms.utils.toBase64 @@ -314,15 +315,15 @@ fun createDeviceEnvInfo(context: Context): DeviceEnvInfo? { gpLastUpdateTime = packageInfo.lastUpdateTime, gpFirstInstallTime = packageInfo.firstInstallTime, gpSourceDir = packageInfo.applicationInfo.sourceDir!!, - device = Build.DEVICE!!, + device = Build.DEVICE ?: "", displayMetrics = getDisplayInfo(context), telephonyData = getTelephonyData(context), - product = Build.PRODUCT!!, - model = Build.MODEL!!, - manufacturer = Build.MANUFACTURER!!, - fingerprint = Build.FINGERPRINT!!, - release = Build.VERSION.RELEASE!!, - brand = Build.BRAND!!, + product = Build.PRODUCT ?: "", + model = Build.MODEL ?: "", + manufacturer = Build.MANUFACTURER ?: "", + fingerprint = Build.FINGERPRINT ?: "", + release = Build.VERSION.RELEASE ?: "", + brand = Build.BRAND ?: "", batteryLevel = getBatteryLevel(context), timeZoneOffset = if (SDK_INT >= 24) TimeZone.getDefault().rawOffset.toLong() else 0, locationData = getLocationData(context), diff --git a/vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt b/vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt index 9835e0e8db..3d66ae1669 100644 --- a/vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt +++ b/vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt @@ -2,7 +2,11 @@ package org.microg.vending.billing.core import android.content.Context import android.net.Uri -import com.android.volley.* +import com.android.volley.DefaultRetryPolicy +import com.android.volley.NetworkResponse +import com.android.volley.Request +import com.android.volley.Response +import com.android.volley.VolleyError import com.android.volley.toolbox.HttpHeaderParser import com.android.volley.toolbox.JsonObjectRequest import com.android.volley.toolbox.Volley @@ -17,7 +21,8 @@ import kotlin.coroutines.suspendCoroutine private const val POST_TIMEOUT = 8000 class HttpClient(context: Context) { - private val requestQueue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) } + + val requestQueue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) } suspend fun get( url: String, @@ -48,8 +53,6 @@ class HttpClient(context: Context) { }.setShouldCache(cache)) } - - suspend fun , O> post( url: String, headers: Map = emptyMap(), diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt b/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt new file mode 100644 index 0000000000..74709ed204 --- /dev/null +++ b/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt @@ -0,0 +1,223 @@ +/** + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.finsky + +import android.annotation.SuppressLint +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Build +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.Action +import androidx.core.app.NotificationManagerCompat +import com.android.vending.R +import com.google.android.finsky.assetmoduleservice.DownloadData +import com.google.android.finsky.assetmoduleservice.getChunkFile +import com.google.android.play.core.assetpacks.model.AssetPackStatus +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Future +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit + +private const val corePoolSize = 0 +private const val maximumPoolSize = 1 +private const val keepAliveTime = 30L + +private const val CHANNEL_ID = "progress_notification_channel" +private const val NOTIFICATION_ID = 1 +private const val CANCEL_ACTION = "CANCEL_DOWNLOAD" + +private const val TAG = "DownloadManager" + +class DownloadManager(private val context: Context) { + + private val notifyBuilderMap = ConcurrentHashMap() + private val downloadingRecord = ConcurrentHashMap>() + + @Volatile + private var shouldStops = false + + private val cancelReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val moduleName = intent.getStringExtra(KEY_MODULE_NAME) + if (moduleName != null) { + cancelDownload(moduleName) + } + } + } + + init { + createNotificationChannel() + val filter = IntentFilter(CANCEL_ACTION) + context.registerReceiver(cancelReceiver, filter) + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel(CHANNEL_ID, "Download Progress", NotificationManager.IMPORTANCE_LOW) + val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + } + + private val executor by lazy { + ThreadPoolExecutor( + corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, LinkedBlockingQueue() + ) { r -> Thread(r).apply { name = "DownloadThread" } } + } + + private fun initNotification(moduleName: String, packageName: String) { + val cancelIntent = Intent(CANCEL_ACTION).apply { + putExtra(KEY_MODULE_NAME, moduleName) + } + val cancelPendingIntent = PendingIntent.getBroadcast( + context, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + val packageManager: PackageManager = context.packageManager + val applicationInfo = packageManager.getApplicationInfo(packageName, 0) + val appName = packageManager.getApplicationLabel(applicationInfo).toString() + val appIcon = packageManager.getApplicationIcon(applicationInfo) + val largeIconBitmap = if (appIcon is BitmapDrawable) { + appIcon.bitmap + } else { + Bitmap.createBitmap(appIcon.intrinsicWidth, appIcon.intrinsicHeight, Bitmap.Config.ARGB_8888).apply { + val canvas = Canvas(this) + appIcon.setBounds(0, 0, canvas.width, canvas.height) + appIcon.draw(canvas) + } + } + + val notifyBuilder = NotificationCompat.Builder(context, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(context.getString(R.string.download_notification_attachment_file, appName)) + .setContentText(context.getString(R.string.download_notification_tips)) + .setLargeIcon(largeIconBitmap) + .setProgress(100, 0, false) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setOngoing(true) + .setOnlyAlertOnce(true) + .addAction(Action.Builder(R.drawable.ic_cancel, context.getString(android.R.string.cancel), cancelPendingIntent).setSemanticAction(Action.SEMANTIC_ACTION_DELETE).build()) + + notifyBuilderMap[moduleName] = notifyBuilder + + NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notifyBuilder.build()) + } + + private fun updateProgress(moduleName: String, progress: Int) { + val notifyBuilder = notifyBuilderMap[moduleName] ?: return + + notifyBuilder.setProgress(100, progress, false) + NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notifyBuilder.build()) + } + + fun shouldStop(shouldStop: Boolean) { + shouldStops = shouldStop + } + + fun prepareDownload(downloadData: DownloadData, moduleName: String) { + Log.d(TAG, "prepareDownload: ${downloadData.packageName}") + initNotification(moduleName, downloadData.packageName) + val future = executor.submit { + val packData = downloadData.getModuleData(moduleName) + downloadData.updateDownloadStatus(moduleName, AssetPackStatus.DOWNLOADING) + for (chunkData in packData.chunks) { + val moduleName: String = chunkData.moduleName + val chunkSourceUri: String = chunkData.chunkSourceUri ?: continue + val destination = chunkData.getChunkFile(context) + startDownload(moduleName, chunkSourceUri, destination, downloadData) + sendBroadcastForExistingFile(context, downloadData, moduleName, chunkData, destination) + } + updateProgress(moduleName, 100) + notifyBuilderMap[moduleName]?.setOngoing(false) + NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) + } + downloadingRecord[moduleName] = future + } + + fun cancelDownload(moduleName: String) { + Log.d(TAG, "Download for module $moduleName has been canceled.") + downloadingRecord[moduleName]?.cancel(true) + shouldStops = true + notifyBuilderMap[moduleName]?.setOngoing(false) + NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) + } + + private fun startDownload(moduleName: String, downloadLink: String, destinationFile: File, downloadData: DownloadData) { + val packData = downloadData.getModuleData(moduleName) + val uri = Uri.parse(downloadLink).toString() + val connection = URL(uri).openConnection() as HttpURLConnection + var bytes: Long = 0 + try { + connection.requestMethod = "GET" + connection.connectTimeout = 20000 + connection.readTimeout = 20000 + connection.connect() + if (connection.responseCode != HttpURLConnection.HTTP_OK) { + throw IOException("Failed to download file: HTTP response code ${connection.responseCode}") + } + if (destinationFile.exists()) { + destinationFile.delete() + } else destinationFile.parentFile?.mkdirs() + + connection.inputStream.use { input -> + FileOutputStream(destinationFile).use { output -> + val buffer = ByteArray(4096) + var bytesRead: Int + while (input.read(buffer).also { bytesRead = it } != -1) { + if (shouldStops) { + Log.d(TAG, "Download interrupted for module: $moduleName") + downloadData.updateDownloadStatus(moduleName, AssetPackStatus.CANCELED) + return + } + output.write(buffer, 0, bytesRead) + bytes += bytesRead.toLong() + downloadData.incrementModuleBytesDownloaded(moduleName, bytesRead.toLong()) + if (bytes >= 1048576) { + val progress = ((packData.bytesDownloaded.toDouble() / packData.totalBytesToDownload.toDouble()) * 100).toInt() + updateProgress(moduleName, progress) + sendBroadcastForExistingFile(context, downloadData, moduleName, null, null) + bytes = 0 + } + } + } + } + } catch (e: Exception) { + Log.e(TAG, "prepareDownload: startDownload error ", e) + downloadData.updateDownloadStatus(moduleName, AssetPackStatus.FAILED) + cancelDownload(moduleName) + downloadData.getModuleData(moduleName).bytesDownloaded = 0 + } finally { + connection.disconnect() + } + } + + companion object { + @SuppressLint("StaticFieldLeak") + @Volatile + private var instance: DownloadManager? = null + fun get(context: Context): DownloadManager { + return instance ?: synchronized(this) { + instance ?: DownloadManager(context.applicationContext).also { instance = it } + } + } + } +} \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AbstractAssetModuleServiceImpl.kt b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AbstractAssetModuleServiceImpl.kt new file mode 100644 index 0000000000..bafb20a8b7 --- /dev/null +++ b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AbstractAssetModuleServiceImpl.kt @@ -0,0 +1,221 @@ +/* + * SPDX-FileCopyrightText: 2024 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.finsky.assetmoduleservice + +import android.content.Context +import android.os.Bundle +import android.os.Parcel +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.vending.VendingPreferences +import com.google.android.finsky.AssetModuleOptions +import com.google.android.finsky.bundleOf +import com.google.android.finsky.get +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode +import com.google.android.play.core.assetpacks.protocol.BundleKeys +import com.google.android.play.core.assetpacks.protocol.IAssetModuleService +import com.google.android.play.core.assetpacks.protocol.IAssetModuleServiceCallback +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +class AssetPackException(val code: @AssetPackErrorCode Int, message: String? = null) : Exception(message ?: "AssetPackException(code=$code)") + +data class StartDownloadParameters(val moduleNames: List, val installedAssetModules: Map, val options: AssetModuleOptions) +data class GetSessionStatesParameters(val installedAssetModules: Map, val options: AssetModuleOptions) +data class NotifyModuleCompletedParameters(val moduleName: String, val sessionId: Int, val options: AssetModuleOptions) +data class NotifySessionFailedParameters(val sessionId: Int, val options: AssetModuleOptions) +data class RequestDownloadInfoParameters(val moduleNames: List, val installedAssetModules: Map, val options: AssetModuleOptions) +data class RemoveModuleParameters(val moduleName: String, val sessionId: Int, val options: AssetModuleOptions) +data class CancelDownloadsParameters(val moduleNames: List, val options: AssetModuleOptions) +data class KeepAliveParameters(val options: AssetModuleOptions) + +data class NotifyChunkTransferredParameters( + val moduleName: String, + val sliceId: String, + val chunkNumber: Int, + val sessionId: Int, + val options: AssetModuleOptions +) + +data class GetChunkFileDescriptorParameters( + val moduleName: String, + val sliceId: String, + val chunkNumber: Int, + val sessionId: Int, + val options: AssetModuleOptions +) + +abstract class AbstractAssetModuleServiceImpl(val context: Context, override val lifecycle: Lifecycle) : IAssetModuleService.Stub(), LifecycleOwner { + private fun List.getModuleNames(): List = mapNotNull { it.get(BundleKeys.MODULE_NAME).takeIf { !it.isNullOrBlank() } } + private fun Bundle?.getInstalledAssetModules(): Map = get(BundleKeys.INSTALLED_ASSET_MODULE).orEmpty() + .map { it.get(BundleKeys.INSTALLED_ASSET_MODULE_NAME) to it.get(BundleKeys.INSTALLED_ASSET_MODULE_VERSION) } + .filter { it.first != null && it.second != null } + .associate { it.first!! to it.second!! } + + private fun Bundle?.getOptions() = AssetModuleOptions( + this.get(BundleKeys.PLAY_CORE_VERSION_CODE, 0), + this.get(BundleKeys.SUPPORTED_COMPRESSION_FORMATS).orEmpty(), + this.get(BundleKeys.SUPPORTED_PATCH_FORMATS).orEmpty(), + ) + + private fun sendError(callback: IAssetModuleServiceCallback?, method: String, errorCode: @AssetPackErrorCode Int) { + Log.w(TAG, "Sending error from $method: $errorCode") + callback?.onError(bundleOf(BundleKeys.ERROR_CODE to errorCode)) + } + + private fun run( + uncheckedPackageName: String?, + method: String, + callback: IAssetModuleServiceCallback?, + parseParameters: (packageName: String) -> T, + logic: suspend (params: T, packageName: String, callback: IAssetModuleServiceCallback?) -> Unit + ) { + val packageName = try { + PackageUtils.getAndCheckCallingPackage(context, uncheckedPackageName)!! + } catch (e: Exception) { + Log.w(TAG, e) + return sendError(callback, method, AssetPackErrorCode.ACCESS_DENIED) + } + + if (!VendingPreferences.isAssetDeliveryEnabled(context)) { + return sendError(callback, method, AssetPackErrorCode.API_NOT_AVAILABLE) + } + + val input = try { + parseParameters(packageName) + } catch (e: AssetPackException) { + return sendError(callback, method, e.code) + } catch (e: Exception) { + Log.w(TAG, e) + return sendError(callback, method, AssetPackErrorCode.INVALID_REQUEST) + } + + Log.d(TAG, "$method[$packageName]${input.toString().substring(input.javaClass.simpleName.length)}") + + lifecycleScope.launchWhenStarted { + try { + logic.invoke(input, packageName, callback) + } catch (e: AssetPackException) { + sendError(callback, method, e.code) + } catch (e: UnsupportedOperationException) { + Log.w(TAG, "Unsupported: $method") + sendError(callback, method, AssetPackErrorCode.API_NOT_AVAILABLE) + } catch (e: Exception) { + Log.w(TAG, e) + sendError(callback, method, AssetPackErrorCode.INTERNAL_ERROR) + } + } + } + + protected abstract fun getDefaultSessionId(packageName: String, moduleName: String): Int + + override fun startDownload(uncheckedPackageName: String?, list: MutableList?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "startDownload", callback, { _ -> + StartDownloadParameters(list!!.getModuleNames().also { require(it.isNotEmpty()) }, bundle.getInstalledAssetModules(), bundle.getOptions()) + }, this::startDownload) + } + + abstract suspend fun startDownload(params: StartDownloadParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun getSessionStates(uncheckedPackageName: String?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "getSessionStates", callback, { _ -> + GetSessionStatesParameters(bundle.getInstalledAssetModules(), bundle.getOptions()) + }, this::getSessionStates) + } + + abstract suspend fun getSessionStates(params: GetSessionStatesParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun notifyChunkTransferred(uncheckedPackageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "notifyChunkTransferred", callback, { packageName -> + val moduleName = bundle.get(BundleKeys.MODULE_NAME)!!.also { require(it.isNotEmpty()) } + NotifyChunkTransferredParameters( + moduleName, + bundle.get(BundleKeys.SLICE_ID)!!.also { require(it.isNotEmpty()) }, + bundle.get(BundleKeys.CHUNK_NUMBER, 0), + bundle.get(BundleKeys.SESSION_ID, getDefaultSessionId(packageName, moduleName)), + bundle2.getOptions() + ) + }, this::notifyChunkTransferred) + } + + abstract suspend fun notifyChunkTransferred(params: NotifyChunkTransferredParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun notifyModuleCompleted(uncheckedPackageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "notifyModuleCompleted", callback, { packageName -> + val moduleName = bundle.get(BundleKeys.MODULE_NAME)!!.also { require(it.isNotEmpty()) } + NotifyModuleCompletedParameters( + moduleName, + bundle.get(BundleKeys.SESSION_ID, getDefaultSessionId(packageName, moduleName)), + bundle2.getOptions() + ) + }, this::notifyModuleCompleted) + } + + abstract suspend fun notifyModuleCompleted(params: NotifyModuleCompletedParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun notifySessionFailed(uncheckedPackageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "notifySessionFailed", callback, { _ -> + NotifySessionFailedParameters(bundle.get(BundleKeys.SESSION_ID, 0), bundle2.getOptions()) + }, this::notifySessionFailed) + } + + abstract suspend fun notifySessionFailed(params: NotifySessionFailedParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun getChunkFileDescriptor(uncheckedPackageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "getChunkFileDescriptor", callback, { packageName -> + val moduleName = bundle.get(BundleKeys.MODULE_NAME)!!.also { require(it.isNotEmpty()) } + GetChunkFileDescriptorParameters( + moduleName, + bundle.get(BundleKeys.SLICE_ID)!!, + bundle.get(BundleKeys.CHUNK_NUMBER, 0), + bundle.get(BundleKeys.SESSION_ID, getDefaultSessionId(packageName, moduleName)), + bundle2.getOptions() + ) + }, this::getChunkFileDescriptor) + } + + abstract suspend fun getChunkFileDescriptor(params: GetChunkFileDescriptorParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun requestDownloadInfo(uncheckedPackageName: String?, list: MutableList?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "requestDownloadInfo", callback, { _ -> + RequestDownloadInfoParameters(list!!.getModuleNames().also { require(it.isNotEmpty()) }, bundle.getInstalledAssetModules(), bundle.getOptions()) + }, this::requestDownloadInfo) + } + + abstract suspend fun requestDownloadInfo(params: RequestDownloadInfoParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun removeModule(uncheckedPackageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "removeModule", callback, { packageName -> + val moduleName = bundle?.get(BundleKeys.MODULE_NAME)!! + RemoveModuleParameters( + moduleName, + bundle.get(BundleKeys.SESSION_ID, getDefaultSessionId(packageName, moduleName)), + bundle2.getOptions() + ) + }, this::removeModule) + } + + abstract suspend fun removeModule(params: RemoveModuleParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun cancelDownloads(uncheckedPackageName: String?, list: MutableList?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { + run(uncheckedPackageName, "cancelDownloads", callback, { _ -> + CancelDownloadsParameters(list!!.getModuleNames().also { require(it.isNotEmpty()) }, bundle.getOptions()) + }, this::cancelDownloads) + } + + abstract suspend fun cancelDownloads(params: CancelDownloadsParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun keepAlive(packageName: String?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { + run(packageName, "keepAlive", callback, { KeepAliveParameters(bundle.getOptions()) }, this::keepAlive) + } + + abstract suspend fun keepAlive(params: KeepAliveParameters, packageName: String, callback: IAssetModuleServiceCallback?) + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) } +} \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt new file mode 100644 index 0000000000..cde513190e --- /dev/null +++ b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt @@ -0,0 +1,224 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.finsky.assetmoduleservice + +import android.accounts.AccountManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.IBinder +import android.os.ParcelFileDescriptor +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleService +import com.google.android.finsky.* +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode +import com.google.android.play.core.assetpacks.model.AssetPackStatus +import com.google.android.play.core.assetpacks.protocol.BundleKeys +import com.google.android.play.core.assetpacks.protocol.IAssetModuleServiceCallback +import org.microg.gms.profile.ProfileManager +import org.microg.vending.billing.core.HttpClient +import java.io.File + +const val TAG = "AssetModuleService" + +class AssetModuleService : LifecycleService() { + private lateinit var httpClient: HttpClient + private lateinit var accountManager: AccountManager + + override fun onBind(intent: Intent): IBinder { + super.onBind(intent) + Log.d(TAG, "onBind: ") + ProfileManager.ensureInitialized(this) + accountManager = AccountManager.get(this) + httpClient = HttpClient(this) + return AssetModuleServiceImpl(this, lifecycle, httpClient, accountManager, packageDownloadData).asBinder() + } + + override fun onDestroy() { + Log.d(TAG, "onDestroy: ") + httpClient.requestQueue.cancelAll(TAG_REQUEST) + super.onDestroy() + } + + companion object { + private val packageDownloadData = mutableMapOf() + } +} + +class AssetModuleServiceImpl( + context: Context, lifecycle: Lifecycle, + private val httpClient: HttpClient, + private val accountManager: AccountManager, + private val packageDownloadData: MutableMap +) : AbstractAssetModuleServiceImpl(context, lifecycle) { + private val fileDescriptorMap = mutableMapOf() + + private fun checkSessionValid(packageName: String, sessionId: Int) { + if (packageDownloadData[packageName]?.sessionIds?.values?.contains(sessionId) != true) { + Log.w(TAG, "No active session with id $sessionId in $packageName") + throw AssetPackException(AssetPackErrorCode.ACCESS_DENIED) + } + } + + override fun getDefaultSessionId(packageName: String, moduleName: String): Int = + packageDownloadData[packageName]?.sessionIds?.get(moduleName) ?: 0 + + override suspend fun startDownload(params: StartDownloadParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + if (packageDownloadData[packageName] == null || + packageDownloadData[packageName]?.packageName != packageName || + packageDownloadData[packageName]?.moduleNames?.intersect(params.moduleNames.toSet())?.isEmpty() == true) { + packageDownloadData[packageName] = httpClient.initAssetModuleData(context, packageName, accountManager, params.moduleNames, params.options) + if (packageDownloadData[packageName] == null) { + throw AssetPackException(AssetPackErrorCode.API_NOT_AVAILABLE) + } + } + params.moduleNames.forEach { + val moduleData = packageDownloadData[packageName]?.getModuleData(it) + if (moduleData?.status != AssetPackStatus.DOWNLOADING && moduleData?.status != AssetPackStatus.COMPLETED) { + packageDownloadData[packageName]?.updateDownloadStatus(it, AssetPackStatus.PENDING) + sendBroadcastForExistingFile(context, packageDownloadData[packageName]!!, it, null, null) + } + if (moduleData?.status == AssetPackStatus.FAILED) { + // FIXME: If we start download later, we shouldn't send a failure callback now + callback?.onError(Bundle().apply { put(BundleKeys.ERROR_CODE, AssetPackErrorCode.NETWORK_ERROR) }) + } + packageDownloadData[packageName]?.getModuleData(it)?.chunks?.forEach { chunkData -> + val destination = chunkData.getChunkFile(context) + if (destination.exists() && destination.length() == chunkData.chunkBytesToDownload) { + sendBroadcastForExistingFile(context, packageDownloadData[packageName]!!, it, chunkData, destination) + } + } + } + val bundleData = buildDownloadBundle(packageDownloadData[packageName]!!, params.moduleNames) + Log.d(TAG, "startDownload: $bundleData") + callback?.onStartDownload(-1, bundleData) + params.moduleNames.forEach { + val packData = packageDownloadData[packageName]?.getModuleData(it) + if (packData?.status == AssetPackStatus.PENDING) { + DownloadManager.get(context).shouldStop(false) + DownloadManager.get(context).prepareDownload(packageDownloadData[packageName]!!, it) + } + } + } + + override suspend fun getSessionStates(params: GetSessionStatesParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + val listBundleData: MutableList = mutableListOf() + + packageDownloadData[packageName]?.moduleNames?.forEach { moduleName -> + if (moduleName in params.installedAssetModules) return@forEach + + listBundleData.add(sendBroadcastForExistingFile(context, packageDownloadData[packageName]!!, moduleName, null, null)) + + packageDownloadData[packageName]?.getModuleData(moduleName)?.chunks?.forEach { chunkData -> + val destination = chunkData.getChunkFile(context) + if (destination.exists() && destination.length() == chunkData.chunkBytesToDownload) { + sendBroadcastForExistingFile(context, packageDownloadData[packageName]!!, moduleName, chunkData, destination) + } + } + } + + Log.d(TAG, "getSessionStates: $listBundleData") + callback?.onGetSessionStates(listBundleData) + } + + override suspend fun notifyChunkTransferred(params: NotifyChunkTransferredParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + checkSessionValid(packageName, params.sessionId) + + val downLoadFile = context.getChunkFile(params.sessionId, params.moduleName, params.sliceId, params.chunkNumber) + fileDescriptorMap[downLoadFile]?.close() + fileDescriptorMap.remove(downLoadFile) + // TODO: Remove chunk after successful transfer of chunk or only with module? + callback?.onNotifyChunkTransferred( + bundleOf(BundleKeys.MODULE_NAME to params.moduleName) + + (BundleKeys.SLICE_ID to params.sliceId) + + (BundleKeys.CHUNK_NUMBER to params.chunkNumber) + + (BundleKeys.SESSION_ID to params.sessionId), + bundleOf(BundleKeys.ERROR_CODE to AssetPackErrorCode.NO_ERROR) + ) + } + + override suspend fun notifyModuleCompleted(params: NotifyModuleCompletedParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + checkSessionValid(packageName, params.sessionId) + + packageDownloadData[packageName]?.updateDownloadStatus(params.moduleName, AssetPackStatus.COMPLETED) + sendBroadcastForExistingFile(context, packageDownloadData[packageName]!!, params.moduleName, null, null) + + val directory = context.getModuleDir(params.sessionId, params.moduleName) + if (directory.exists()) { + directory.deleteRecursively() + Log.d(TAG, "Directory $directory deleted successfully.") + } else { + Log.d(TAG, "Directory $directory does not exist.") + } + callback?.onNotifyModuleCompleted( + bundleOf(BundleKeys.MODULE_NAME to params.moduleName) + + (BundleKeys.SESSION_ID to params.sessionId), + bundleOf(BundleKeys.ERROR_CODE to AssetPackErrorCode.NO_ERROR) + ) + } + + override suspend fun notifySessionFailed(params: NotifySessionFailedParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + checkSessionValid(packageName, params.sessionId) + + // TODO: Implement + callback?.onNotifySessionFailed(bundleOf(BundleKeys.SESSION_ID to params.sessionId)) + //throw UnsupportedOperationException() + } + + override suspend fun keepAlive(params: KeepAliveParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + // TODO: Implement + // Not throwing an exception is the better fallback implementation + } + + override suspend fun getChunkFileDescriptor(params: GetChunkFileDescriptorParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + checkSessionValid(packageName, params.sessionId) + + val downLoadFile = context.getChunkFile(params.sessionId, params.moduleName, params.sliceId, params.chunkNumber) + val parcelFileDescriptor = ParcelFileDescriptor.open(downLoadFile, ParcelFileDescriptor.MODE_READ_ONLY).also { + fileDescriptorMap[downLoadFile] = it + } + + Log.d(TAG, "getChunkFileDescriptor -> $parcelFileDescriptor") + callback?.onGetChunkFileDescriptor( + bundleOf(BundleKeys.CHUNK_FILE_DESCRIPTOR to parcelFileDescriptor), + bundleOf(BundleKeys.ERROR_CODE to AssetPackErrorCode.NO_ERROR) + ) + } + + override suspend fun requestDownloadInfo(params: RequestDownloadInfoParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + if (packageDownloadData[packageName] == null || + packageDownloadData[packageName]?.packageName != packageName || + packageDownloadData[packageName]?.moduleNames?.intersect(params.moduleNames.toSet())?.isEmpty() == true) { + packageDownloadData[packageName] = httpClient.initAssetModuleData(context, packageName, accountManager, params.moduleNames, params.options) + if (packageDownloadData[packageName] == null) { + throw AssetPackException(AssetPackErrorCode.API_NOT_AVAILABLE) + } + } + params.moduleNames.forEach { + val packData = packageDownloadData[packageName]?.getModuleData(it) + if (packData?.status == AssetPackStatus.FAILED) { + // FIXME: If we start download later, we shouldn't send a failure callback now + callback?.onError(Bundle().apply { put(BundleKeys.ERROR_CODE, AssetPackErrorCode.NETWORK_ERROR) }) + } + } + val bundleData = buildDownloadBundle(packageDownloadData[packageName]!!, params.moduleNames) + Log.d(TAG, "requestDownloadInfo -> $bundleData") + callback?.onRequestDownloadInfo(bundleData, bundleData) + } + + override suspend fun removeModule(params: RemoveModuleParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + checkSessionValid(packageName, params.sessionId) + // TODO: Implement + throw UnsupportedOperationException() + } + + override suspend fun cancelDownloads(params: CancelDownloadsParameters, packageName: String, callback: IAssetModuleServiceCallback?) { + // TODO: Implement + callback?.onCancelDownloads(bundleOf(BundleKeys.ERROR_CODE to AssetPackErrorCode.NO_ERROR)) + //throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/DownloadData.kt b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/DownloadData.kt new file mode 100644 index 0000000000..ccc3f7c339 --- /dev/null +++ b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/DownloadData.kt @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.finsky.assetmoduleservice + +import android.content.Context +import com.google.android.finsky.getChunkFile +import com.google.android.play.core.assetpacks.protocol.CompressionFormat +import java.io.File +import java.io.Serializable + +data class DownloadData( + var packageName: String = "", + var errorCode: Int = 0, + var sessionIds: Map = emptyMap(), + var status: Int = 0, + var moduleNames: Set = emptySet(), + var appVersionCode: Long = 0, + var moduleDataMap: Map = emptyMap() +) : Serializable { + + fun getModuleData(moduleName: String): ModuleData { + return moduleDataMap[moduleName] ?: throw IllegalArgumentException("ModuleData for moduleName '$moduleName' not found.") + } + + fun incrementModuleBytesDownloaded(packName: String, bytes: Long) { + getModuleData(packName).incrementBytesDownloaded(bytes) + } + + fun updateDownloadStatus(packName: String, statusCode: Int) { + getModuleData(packName).apply { + status = statusCode + } + } +} + +fun DownloadData?.merge(data: DownloadData?): DownloadData? { + if (this == null) return data + if (data == null) return this + moduleNames += data.moduleNames + sessionIds += data.sessionIds.filter { it.key !in sessionIds.keys } + moduleDataMap += data.moduleDataMap.filter { it.key !in moduleDataMap.keys } + return this +} + +data class ModuleData( + var packVersionCode: Long = 0, + var moduleVersion: Long = 0, + var errorCode: Int = 0, + var status: Int = 0, + var bytesDownloaded: Long = 0, + var totalBytesToDownload: Long = 0, + var chunks: List = emptyList(), + var sliceIds: ArrayList? = null +) : Serializable { + fun incrementBytesDownloaded(bytes: Long) { + bytesDownloaded += bytes + } +} + +data class ChunkData( + var sessionId: Int, + val moduleName: String, + val sliceId: String, + val chunkSourceUri: String?, + val chunkBytesToDownload: Long, + val chunkIndex: Int, + val sliceCompressionFormat: @CompressionFormat Int, + val sliceUncompressedSize: Long, + val sliceUncompressedHashSha256: String?, + val numberOfChunksInSlice: Int +) + +fun ChunkData.getChunkFile(context: Context) = context.getChunkFile(sessionId, moduleName, sliceId, chunkIndex) \ No newline at end of file diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt b/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt new file mode 100644 index 0000000000..4a1e53664d --- /dev/null +++ b/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt @@ -0,0 +1,312 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.finsky + +import android.accounts.Account +import android.accounts.AccountManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.util.Log +import androidx.collection.ArraySet +import androidx.collection.arrayMapOf +import androidx.collection.arraySetOf +import androidx.core.content.pm.PackageInfoCompat +import com.android.vending.licensing.AUTH_TOKEN_SCOPE +import com.android.vending.licensing.getAuthToken +import com.android.vending.licensing.getLicenseRequestHeaders +import com.google.android.finsky.assetmoduleservice.AssetPackException +import com.google.android.finsky.assetmoduleservice.DownloadData +import com.google.android.finsky.assetmoduleservice.ModuleData +import com.google.android.finsky.assetmoduleservice.ChunkData +import com.google.android.play.core.assetpacks.model.AssetPackErrorCode +import com.google.android.play.core.assetpacks.model.AssetPackStatus +import com.google.android.play.core.assetpacks.protocol.BroadcastConstants +import com.google.android.play.core.assetpacks.protocol.BundleKeys +import com.google.android.play.core.assetpacks.protocol.CompressionFormat +import com.google.android.play.core.assetpacks.protocol.PatchFormat +import org.microg.gms.auth.AuthConstants +import org.microg.vending.billing.GServices +import org.microg.vending.billing.core.HttpClient +import java.io.File +import java.util.Collections + +const val KEY_MODULE_NAME = "module_name" + +const val TAG_REQUEST = "asset_module" + +private const val ASSET_MODULE_DELIVERY_URL = "https://play-fe.googleapis.com/fdfe/assetModuleDelivery" + +private const val TAG = "AssetModuleRequest" + +fun getAppVersionCode(context: Context, packageName: String): Long? { + return try { + PackageInfoCompat.getLongVersionCode(context.packageManager.getPackageInfo(packageName, 0)) + } catch (e: PackageManager.NameNotFoundException) { + throw AssetPackException(AssetPackErrorCode.APP_UNAVAILABLE, e.message) + } +} + +fun Bundle?.get(key: BundleKeys.RootKey): T? = if (this == null) null else BundleKeys.get(this, key) +fun Bundle?.get(key: BundleKeys.RootKey, def: T): T = if (this == null) def else BundleKeys.get(this, key, def) +fun Bundle.put(key: BundleKeys.RootKey, v: T) = BundleKeys.put(this, key, v) +fun Bundle.put(key: BundleKeys.PackKey, packName: String, v: T) = BundleKeys.put(this, key, packName, v) +fun Bundle.put(key: BundleKeys.SliceKey, packName: String, sliceId: String, v: T) = BundleKeys.put(this, key, packName, sliceId, v) +fun bundleOf(pair: Pair, T>): Bundle = Bundle().apply { put(pair.first, pair.second) } +operator fun Bundle.plus(other: Bundle): Bundle = Bundle(this).apply { putAll(other) } +operator fun Bundle.plus(pair: Pair, T>): Bundle = this + bundleOf(pair) + +val Context.assetPacksDir: File + get() = File(filesDir, "assetpacks") +fun Context.getSessionDir(sessionId: Int) = + File(assetPacksDir, sessionId.toString()) +fun Context.getModuleDir(sessionId: Int, moduleName: String): File = + File(getSessionDir(sessionId), moduleName) +fun Context.getSliceDir(sessionId: Int, moduleName: String, sliceId: String) = + File(getModuleDir(sessionId, moduleName), sliceId) +fun Context.getChunkFile(sessionId: Int, moduleName: String, sliceId: String, chunkNumber: Int): File = + File(getSliceDir(sessionId, moduleName, sliceId), chunkNumber.toString()) + +data class AssetModuleOptions(val playCoreVersionCode: Int, val supportedCompressionFormats: List, val supportedPatchFormats: List) + +suspend fun HttpClient.initAssetModuleData( + context: Context, + packageName: String, + accountManager: AccountManager, + requestedAssetModuleNames: List, + options: AssetModuleOptions, + playCoreVersionCode: Int = options.playCoreVersionCode, + supportedCompressionFormats: List = options.supportedCompressionFormats.takeIf { it.isNotEmpty() } ?: listOf(CompressionFormat.UNSPECIFIED, CompressionFormat.CHUNKED_GZIP), + supportedPatchFormats: List = options.supportedPatchFormats.takeIf { it.isNotEmpty() } ?: listOf(PatchFormat.PATCH_GDIFF, PatchFormat.GZIPPED_GDIFF), +): DownloadData? { + val accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) + var oauthToken: String? = null + if (accounts.isEmpty()) { + return null + } else { + for (account: Account in accounts) { + oauthToken = accountManager.getAuthToken(account, AUTH_TOKEN_SCOPE, false).getString(AccountManager.KEY_AUTHTOKEN) + if (oauthToken != null) { + break + } + } + } + + if (oauthToken == null) { + return null + } + + val appVersionCode = getAppVersionCode(context, packageName) + val requestPayload = AssetModuleDeliveryRequest.Builder().callerInfo(CallerInfo(appVersionCode)).packageName(packageName) + .playCoreVersion(playCoreVersionCode).supportedCompressionFormats(supportedCompressionFormats) + .supportedPatchFormats(supportedPatchFormats).modules(ArrayList().apply { + requestedAssetModuleNames.forEach { add(AssetModuleInfo.Builder().name(it).build()) } + }).build() + + val androidId = GServices.getString(context.contentResolver, "android_id", "0")?.toLong() ?: 1 + + val moduleDeliveryInfo = post( + url = ASSET_MODULE_DELIVERY_URL, + headers = getLicenseRequestHeaders(oauthToken, androidId), + payload = requestPayload, + adapter = AssetModuleDeliveryResponse.ADAPTER + ).wrapper?.deliveryInfo + Log.d(TAG, "initAssetModuleData: moduleDeliveryInfo-> $moduleDeliveryInfo") + return initModuleDownloadInfo(packageName, appVersionCode, moduleDeliveryInfo) +} + +private val sessionIdMap: MutableMap = mutableMapOf() + +private val lock = Any() +private fun Context.generateSessionId(): Int { + synchronized(lock) { + val sharedPreferences = getSharedPreferences("AssetModuleSessionIdGenerator", 0) + val latest = sharedPreferences.getInt("Latest", 0) + 1 + val edit = sharedPreferences.edit() + edit.putInt("Latest", latest) + edit.commit() + return latest + } +} + +private fun getSessionIdForPackage(packageName: String): Int { + synchronized(lock) { + return sessionIdMap.getOrPut(packageName) { 10 } + } +} + +private fun updateSessionIdForPackage(packageName: String, increment: Int) { + synchronized(lock) { + val currentSessionId = sessionIdMap[packageName] ?: 10 + sessionIdMap[packageName] = currentSessionId + increment + } +} + +private fun initModuleDownloadInfo(packageName: String, appVersionCode: Long?, deliveryInfo: ModuleDeliveryInfo?): DownloadData? { + if (deliveryInfo == null || deliveryInfo.status != null) { + return null + } + val moduleNames: ArraySet = arraySetOf() + var totalBytesToDownload = 0L + var packVersionCode = 0L + val sessionIds = arrayMapOf() + val moduleDataMap = arrayMapOf() + val baseSessionId = getSessionIdForPackage(packageName) + for (moduleIndex in deliveryInfo.modules.indices) { + val moduleInfo: ModuleInfo = deliveryInfo.modules[moduleIndex] + packVersionCode = moduleInfo.packVersion ?: 0 + val slices: List = moduleInfo.slices + val moduleName: String = moduleInfo.moduleName ?: continue + var moduleBytesToDownload = 0L + moduleNames.add(moduleName) + sessionIds[moduleName] = baseSessionId + moduleIndex + var totalSumOfSubcontractedModules = 0 + val sliceIds: ArrayList = ArrayList() + val chunkDatas: ArrayList = arrayListOf() + for (sliceIndex in slices.indices) { + val sliceInfo: SliceInfo = slices[sliceIndex] + if (sliceInfo.metadata == null || sliceInfo.fullDownloadInfo == null) { + continue + } + val chunks = sliceInfo.fullDownloadInfo.chunks + val numberOfChunks = chunks.size + val uncompressedSize = sliceInfo.fullDownloadInfo.uncompressedSize + val uncompressedHashSha256 = sliceInfo.fullDownloadInfo.uncompressedHashSha256 + val sliceId = sliceInfo.metadata.sliceId?.also { sliceIds.add(it) } ?: continue + var sliceBytesToDownload = 0L + for (chunkIndex in chunks.indices) { + val dResource: ChunkInfo = chunks[chunkIndex] + sliceBytesToDownload += dResource.bytesToDownload!! + totalSumOfSubcontractedModules += 1 + chunkDatas.add(ChunkData( + sessionId = sessionIds[moduleName]!!, + moduleName = moduleName, + sliceId = sliceId, + chunkSourceUri = dResource.sourceUri, + chunkBytesToDownload = dResource.bytesToDownload, + chunkIndex = chunkIndex, + sliceCompressionFormat = sliceInfo.fullDownloadInfo.compressionFormat ?: CompressionFormat.UNSPECIFIED, + sliceUncompressedSize = uncompressedSize ?: 0, + sliceUncompressedHashSha256 = uncompressedHashSha256, + numberOfChunksInSlice = numberOfChunks + )) + } + moduleBytesToDownload += sliceBytesToDownload + } + val moduleData = ModuleData( + packVersionCode = packVersionCode, + moduleVersion = 0, + errorCode = AssetPackErrorCode.NO_ERROR, + status = AssetPackStatus.NOT_INSTALLED, + bytesDownloaded = 0, + totalBytesToDownload = moduleBytesToDownload, + chunks = chunkDatas, + sliceIds = sliceIds + ) + totalBytesToDownload += moduleBytesToDownload + moduleDataMap[moduleName] = moduleData + } + updateSessionIdForPackage(packageName, deliveryInfo.modules.size) + return DownloadData( + packageName = packageName, + errorCode = AssetPackErrorCode.NO_ERROR, + sessionIds = sessionIds, + status = AssetPackStatus.NOT_INSTALLED, + moduleNames = moduleNames, + appVersionCode = appVersionCode ?: packVersionCode, + moduleDataMap + ) +} + +fun buildDownloadBundle(downloadData: DownloadData, list: List? = null): Bundle { + val bundleData = Bundle() + val arrayList = arrayListOf() + var totalBytesToDownload = 0L + var bytesDownloaded = 0L + + list?.forEach { moduleName -> + val packData = downloadData.getModuleData(moduleName) + bundleData.put(BundleKeys.STATUS, packData.status) + downloadData.sessionIds[moduleName]?.let { sessionId -> + bundleData.put(BundleKeys.SESSION_ID, sessionId) + bundleData.put(BundleKeys.SESSION_ID, moduleName, packData.status) + } + bundleData.put(BundleKeys.PACK_VERSION_TAG, moduleName, null) + bundleData.put(BundleKeys.STATUS, moduleName, packData.status) + bundleData.put(BundleKeys.ERROR_CODE, moduleName, packData.errorCode) + bundleData.put(BundleKeys.PACK_VERSION, moduleName, packData.packVersionCode) + bundleData.put(BundleKeys.PACK_BASE_VERSION, moduleName, packData.moduleVersion) + bundleData.put(BundleKeys.BYTES_DOWNLOADED, moduleName, packData.bytesDownloaded) + bundleData.put(BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, moduleName, packData.totalBytesToDownload) + arrayList.add(moduleName) + totalBytesToDownload += packData.totalBytesToDownload + bytesDownloaded += packData.bytesDownloaded + } + bundleData.put(BundleKeys.ERROR_CODE, downloadData.errorCode) + bundleData.put(BundleKeys.PACK_NAMES, arrayList) + bundleData.put(BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, totalBytesToDownload) + bundleData.put(BundleKeys.BYTES_DOWNLOADED, bytesDownloaded) + return bundleData +} + +fun sendBroadcastForExistingFile(context: Context, downloadData: DownloadData, moduleName: String, chunkData: ChunkData?, destination: File?): Bundle { + val packData = downloadData.getModuleData(moduleName) + try { + val downloadBundle = Bundle() + downloadBundle.put(BundleKeys.APP_VERSION_CODE, downloadData.appVersionCode.toInt()) + downloadBundle.put(BundleKeys.ERROR_CODE, AssetPackErrorCode.NO_ERROR) + downloadBundle.put(BundleKeys.SESSION_ID, downloadData.sessionIds[moduleName] ?: downloadData.status) + downloadBundle.put(BundleKeys.STATUS, packData.status) + downloadBundle.put(BundleKeys.PACK_NAMES, arrayListOf(moduleName)) + downloadBundle.put(BundleKeys.BYTES_DOWNLOADED, packData.bytesDownloaded) + downloadBundle.put(BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, packData.totalBytesToDownload) + downloadBundle.put(BundleKeys.TOTAL_BYTES_TO_DOWNLOAD, moduleName, packData.totalBytesToDownload) + downloadBundle.put(BundleKeys.PACK_VERSION, moduleName, packData.packVersionCode) + downloadBundle.put(BundleKeys.STATUS, moduleName, packData.status) + downloadBundle.put(BundleKeys.ERROR_CODE, moduleName, AssetPackErrorCode.NO_ERROR) + downloadBundle.put(BundleKeys.BYTES_DOWNLOADED, moduleName, packData.bytesDownloaded) + downloadBundle.put(BundleKeys.PACK_BASE_VERSION, moduleName, packData.moduleVersion) + downloadBundle.put(BundleKeys.PACK_VERSION_TAG, moduleName, null) + packData.chunks.map { it.copy() }.forEach { + val sliceId = it.sliceId + val chunkIntents = ArrayList(Collections.nCopies(it.numberOfChunksInSlice, null)) + if (chunkData != null && destination != null) { + val uri = Uri.fromFile(destination) + context.grantUriPermission(moduleName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(uri, context.contentResolver.getType(uri)) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + if (destination.exists() && chunkData.moduleName == moduleName && chunkData.sliceId == sliceId) { + if (chunkIntents[chunkData.chunkIndex] == null) { + chunkIntents[chunkData.chunkIndex] = intent + } + } + } + downloadBundle.put(BundleKeys.CHUNK_INTENTS, moduleName, sliceId, chunkIntents) + downloadBundle.put(BundleKeys.UNCOMPRESSED_SIZE, moduleName, sliceId, it.sliceUncompressedSize) + downloadBundle.put(BundleKeys.COMPRESSION_FORMAT, moduleName, sliceId, it.sliceCompressionFormat) + downloadBundle.put(BundleKeys.UNCOMPRESSED_HASH_SHA256, moduleName, sliceId, it.sliceUncompressedHashSha256) + } + downloadBundle.put(BundleKeys.SLICE_IDS, moduleName, ArrayList(packData.chunks.map { it.sliceId }.distinct())) + sendBroadCast(context, downloadData, downloadBundle) + return downloadBundle + } catch (e: Exception) { + Log.w(TAG, "sendBroadcastForExistingFile error:" + e.message) + return Bundle(Bundle().apply { put(BundleKeys.ERROR_CODE, AssetPackErrorCode.API_NOT_AVAILABLE) }) + } +} + +private fun sendBroadCast(context: Context, downloadData: DownloadData, result: Bundle) { + val intent = Intent() + intent.setAction(BroadcastConstants.ACTION_SESSION_UPDATE) + intent.putExtra(BroadcastConstants.EXTRA_SESSION_STATE, result) + intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + intent.putExtra(BroadcastConstants.EXTRA_FLAGS, Bundle().apply { putBoolean(BroadcastConstants.KEY_USING_EXTRACTOR_STREAM, true) }) + intent.setPackage(downloadData.packageName) + context.sendBroadcast(intent) +} diff --git a/vending-app/src/main/proto/AssetModule.proto b/vending-app/src/main/proto/AssetModule.proto new file mode 100644 index 0000000000..deaa3d0c59 --- /dev/null +++ b/vending-app/src/main/proto/AssetModule.proto @@ -0,0 +1,75 @@ +option java_package = "com.google.android.finsky"; +option java_multiple_files = true; + +import "Timestamp.proto"; + +message AssetModuleDeliveryRequest { + optional string packageName = 1; + optional CallerInfo callerInfo = 2; + optional uint32 playCoreVersion = 3; + repeated uint32 supportedCompressionFormats = 4; + repeated uint32 supportedPatchFormats = 5; + repeated AssetModuleInfo modules = 6; + optional bool isInstantApp = 7; +} + +message CallerInfo { + oneof Version { + int64 appVersionCode = 1; + string internalSharingId = 3; + } +} + +message AssetModuleInfo { + optional string name = 1; + optional int64 type = 2; +} + +message AssetModuleDeliveryResponse { + optional ModuleDeliveryWrapper wrapper = 1; +} + +message ModuleDeliveryWrapper { + optional ModuleDeliveryInfo deliveryInfo = 151; +} + +message ModuleDeliveryInfo { + repeated ModuleInfo modules = 3; + optional int32 status = 4; +} + +message ModuleInfo { + optional string moduleName = 1; + optional int64 packVersion = 2; + repeated SliceInfo slices = 3; + optional int64 packBaseVersion = 4; + optional string packVersionTag = 5; +} + +message SliceInfo { + optional SliceMetadata metadata = 1; + optional FullDownloadInfo fullDownloadInfo = 2; + optional PatchInfo patchInfo = 3; +} + +message SliceMetadata { + optional string sliceId = 1; +} + +message FullDownloadInfo { + optional int64 uncompressedSize = 1; + optional string uncompressedHashSha256 = 2; + optional uint32 compressionFormat = 3; + repeated ChunkInfo chunks = 4; +} + +message PatchInfo { + optional uint32 patchFormat = 1; + repeated ChunkInfo chunks = 2; +} + +message ChunkInfo { + optional int64 bytesToDownload = 1; + optional string sha256 = 2; + optional string sourceUri = 3; +} diff --git a/vending-app/src/main/res/drawable/ic_cancel.xml b/vending-app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000000..55a4375b31 --- /dev/null +++ b/vending-app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/vending-app/src/main/res/values-ar/strings.xml b/vending-app/src/main/res/values-ar/strings.xml index 0a56150062..849953629f 100644 --- a/vending-app/src/main/res/values-ar/strings.xml +++ b/vending-app/src/main/res/values-ar/strings.xml @@ -1,4 +1,22 @@ مرافق مايكرو-جي + لا يمكن استخدام مرافق مايكرو-جي بشكل مستقل. يرجى تثبيت خدمات مايكرو-جي لاستخدام مايكرو-جي. + لا يمكن استخدام مرافق مايكرو-جي بشكل مستقل. تم فتح إعدادات خدمات مايكرو-جي بدلًا من ذلك. + إشعارات التراخيص + تعذر على %1$s التحقق من الترخيص + إن قام التطبيق بتصرفات غير متوقعة، فسجل الدخول إلى حساب جوجل الذي اشتريت التطبيق بواسطته. + سَجِّل الدخول + تجاهل + الدفع غير متاح حاليًا + تأكيد عملية الشراء + أنت غير متصلٍ بالإنترنت. يرجى التأكد من تشغيل شبكة الـ Wi-Fi أو شبكة الجوال أَعِد المحاولة. + كلمة المرور غير صحيحة. + حدث خطأ مجهول، يرجى الخروج والمحاولة مرة أخرى. + أدخل كلمة المرور + تذكر تسجيل دخولي على هذا الجهاز + هل نسيت كلمة المرور؟ + معرفة المزيد + تحقق + يتم إشعارك عندما يحاول تطبيق من التحقق من صحة ترخيصه، ولكنك لم تقم بتسجيل الدخول إلى أي حساب جوجل. \ No newline at end of file diff --git a/vending-app/src/main/res/values-cs/strings.xml b/vending-app/src/main/res/values-cs/strings.xml index 88374392c3..a3c27f55c9 100644 --- a/vending-app/src/main/res/values-cs/strings.xml +++ b/vending-app/src/main/res/values-cs/strings.xml @@ -19,4 +19,6 @@ Přihlásit se Zadejte své heslo Zapomněli jste heslo? + Stahování + Dodatečné soubory pro %s \ No newline at end of file diff --git a/vending-app/src/main/res/values-es/strings.xml b/vending-app/src/main/res/values-es/strings.xml index 2807722f08..60f8e9cca2 100644 --- a/vending-app/src/main/res/values-es/strings.xml +++ b/vending-app/src/main/res/values-es/strings.xml @@ -19,4 +19,6 @@ Ingresa tu contraseña Recordar el inicio de sesión en este dispositivo Verificar + Archivos adicionales para %s + Descargando \ No newline at end of file diff --git a/vending-app/src/main/res/values-fr/strings.xml b/vending-app/src/main/res/values-fr/strings.xml index 2b73eac077..1d7f0f3073 100644 --- a/vending-app/src/main/res/values-fr/strings.xml +++ b/vending-app/src/main/res/values-fr/strings.xml @@ -6,4 +6,19 @@ S\'identifier Notifications de licence Ignorer + Compagnon microG + Pay actuellement impossible + Confirmer l\'achat + Non connecté à internet. Merci de vérifier que le Wi-Fi ou les données mobiles sont actifs et réessayer. + Le mot de passe saisi est incorrect. + Erreur inconnue, merci de quitter et réessayer. + Saisir le mot de passe + Se souvenir de moi sur cet appareil + Mot de passe oublié ? + En savoir plus + Vérifier + Le compagnon microG ne peut pas être utilisé seul. Merci d\'installer les services microG pour utiliser microG. + Le compagnon microG ne peut pas être directement utilisé. Consultez plutôt les paramètres des services microG. + Fichiers additionnels pour %s + Téléchargement en cours \ No newline at end of file diff --git a/vending-app/src/main/res/values-ga/strings.xml b/vending-app/src/main/res/values-ga/strings.xml index cdbf50124e..21fd1c76ed 100644 --- a/vending-app/src/main/res/values-ga/strings.xml +++ b/vending-app/src/main/res/values-ga/strings.xml @@ -19,4 +19,6 @@ Tá an pasfhocal a d\'iontráil tú mícheart. Cuimhnigh ar mo logáil isteach ar an ngléas seo Fíoraigh + Ag íosluchtú + Comhaid bhreise le haghaidh %s \ No newline at end of file diff --git a/vending-app/src/main/res/values-lzh/strings.xml b/vending-app/src/main/res/values-lzh/strings.xml index a6b3daec93..955dcd8b5e 100644 --- a/vending-app/src/main/res/values-lzh/strings.xml +++ b/vending-app/src/main/res/values-lzh/strings.xml @@ -1,2 +1,5 @@ - \ No newline at end of file + + microG不可獨用,開啟時當引至microG服務設定界面。 + MicroG假商店 + \ No newline at end of file diff --git a/vending-app/src/main/res/values-pl/strings.xml b/vending-app/src/main/res/values-pl/strings.xml index e19890dbda..172c7a790f 100644 --- a/vending-app/src/main/res/values-pl/strings.xml +++ b/vending-app/src/main/res/values-pl/strings.xml @@ -19,4 +19,6 @@ %1$s nie może zweryfikować licencji Jeśli aplikacja działa nieprawidłowo, zaloguj się na konto Google, za pomocą którego aplikacja została zakupiona. Zaloguj się + Dodatkowe pliki dla %s + Pobieranie \ No newline at end of file diff --git a/vending-app/src/main/res/values-pt-rBR/strings.xml b/vending-app/src/main/res/values-pt-rBR/strings.xml index da66c469cf..d6ae23fc43 100644 --- a/vending-app/src/main/res/values-pt-rBR/strings.xml +++ b/vending-app/src/main/res/values-pt-rBR/strings.xml @@ -19,4 +19,6 @@ Lembre da minha sessão neste dispositivo Esqueceu a senha? Aprenda mais + Baixando + Arquivos adicionais para %s \ No newline at end of file diff --git a/vending-app/src/main/res/values-ro/strings.xml b/vending-app/src/main/res/values-ro/strings.xml index 294959707a..fa9518eb05 100644 --- a/vending-app/src/main/res/values-ro/strings.xml +++ b/vending-app/src/main/res/values-ro/strings.xml @@ -19,4 +19,6 @@ Memorizează autentificarea mea pe acest dispozitiv Află mai multe Verifică + Se descarcă + Fișiere suplimentare pentru %s \ No newline at end of file diff --git a/vending-app/src/main/res/values-sv/strings.xml b/vending-app/src/main/res/values-sv/strings.xml index ba5313b49c..f0cf90cc79 100644 --- a/vending-app/src/main/res/values-sv/strings.xml +++ b/vending-app/src/main/res/values-sv/strings.xml @@ -19,4 +19,6 @@ Glömt lösenordet? Läs mer Verifiera + Fler filer för %s + Laddar ner \ No newline at end of file diff --git a/vending-app/src/main/res/values-th/strings.xml b/vending-app/src/main/res/values-th/strings.xml new file mode 100644 index 0000000000..18a586f6b0 --- /dev/null +++ b/vending-app/src/main/res/values-th/strings.xml @@ -0,0 +1,22 @@ + + + %1$s ไม่สามารถตรวจสอบใบอนุญาตได้ + หากแอปทำงานผิดปกติ ให้ลงชื่อเข้าใช้บัญชี Google ที่คุณใช้ซื้อแอป + เข้าสู่ระบบ + microG Companion + microG Companion ไม่สามารถใช้งานแบบสแตนด์อโลนได้ ให้เปิดการตั้งค่าบริการจาก microG แทน + microG Companion ไม่สามารถใช้งานแบบสแตนด์อโลนได้ โปรดติดตั้งบริการของ microG เพื่อเข้าใช้งาน + การแจ้งเตือนใบอนุญาต + แจ้งเตือนเมื่อแอปพยายามตรวจสอบใบอนุญาต แต่คุณไม่ได้ลงชื่อเข้าใช้บัญชี Google ใดๆ + เพิกเฉย + ชำระเงินไม่ได้ ณ ขณะนี้ + ยืนยันการชำระเงิน + ไม่ได้เชื่อมต่ออินเทอร์เน็ต โปรดตรวจสอบให้แน่ใจว่าเปิด Wi-Fi หรือเครือข่ายมือถือแล้วลองอีกครั้ง + รหัสผ่านที่คุณป้อนไม่ถูกต้อง + ข้อผิดพลาดที่ไม่รู้จัก กรุณาออกแล้วลองอีกครั้ง + กรอกรหัสผ่านของคุณ + จดจำการเข้าสู่ระบบของฉันบนอุปกรณ์นี้ + ลืมรหัสผ่าน? + เรียนรู้เพิ่มเติม + ตรวจสอบ + \ No newline at end of file diff --git a/vending-app/src/main/res/values-tr/strings.xml b/vending-app/src/main/res/values-tr/strings.xml index a6b3daec93..af2251627e 100644 --- a/vending-app/src/main/res/values-tr/strings.xml +++ b/vending-app/src/main/res/values-tr/strings.xml @@ -1,2 +1,22 @@ - \ No newline at end of file + + microG Servisleri tek başına kullanılamaz. microG\'yi kullanmak için lütfen microG Servisleri\'ni yükleyin. + %1$s lisansı doğrulanamadı + Eğer uygulama hatalı davranıyorsa, uygulamayı satın aldığınız Google hesabınız ile giriş yapın. + Yoksay + Ödeme henüz mümkün değil + Ödemeyi onayla + İnternete bağlı değilsiniz. Lütfen Wi-Fi veya mobil verinin açık olduğundan emin olun ve ardından tekrar deneyin. + Bilinmeyen hata, lütfen çıkın ve tekrar deneyin. + Şifrenizi girin + Bu cihazdaki oturumumu hatırla + Şifrenizi mi unuttunuz? + Daha fazla bilgi + Bir uygulama lisansını doğrulamaya çalıştığında ancak siz herhangi bir Google hesabında oturum açmadığınızda bildirim gönderir. + Giriş yap + microG Eşlikçisi tek başına kullanılamaz. Onun yerine microG Servisleri ayarları açılıyor. + Lisans bildirimleri + Girdiğiniz şifre yanlış. + Onayla + microG Eşlikçisi + \ No newline at end of file diff --git a/vending-app/src/main/res/values-uk/strings.xml b/vending-app/src/main/res/values-uk/strings.xml index ef391da33a..c501e7f8aa 100644 --- a/vending-app/src/main/res/values-uk/strings.xml +++ b/vending-app/src/main/res/values-uk/strings.xml @@ -19,4 +19,6 @@ %1$s не вдалося підтвердити ліцензію Увійти Ігнорувати + Додаткові файли для %s + Завантаження \ No newline at end of file diff --git a/vending-app/src/main/res/values-vi/strings.xml b/vending-app/src/main/res/values-vi/strings.xml index a6b3daec93..35e42d3617 100644 --- a/vending-app/src/main/res/values-vi/strings.xml +++ b/vending-app/src/main/res/values-vi/strings.xml @@ -1,2 +1,24 @@ - \ No newline at end of file + + Đối tác microG + Không thể sử dụng Đối tác microG một cách độc lập. Thay vào đó, hãy mở cài đặt Dịch vụ microG. + Không thể sử dụng Đối tác microG một cách độc lập. Vui lòng cài đặt Dịch vụ microG để tiếp tục sử dụng. + Thông báo giấy phép + Thông báo khi ứng dụng cố gắng xác thực giấy phép nhưng bạn chưa đăng nhập vào bất kỳ tài khoản Google nào. + %1$s không thể xác minh giấy phép + Nếu ứng dụng hoạt động bất thường, hãy đăng nhập vào tài khoản Google mà bạn đã dùng để mua ứng dụng. + Đăng nhập + Bỏ qua + Hiện tại không thể thanh toán + Xác nhận mua hàng + Không kết nối được với Internet. Vui lòng kết nối Wi-Fi hoặc dữ liệu di động và thử lại. + Mật khẩu bạn nhập không đúng. + Lỗi không xác định, vui lòng thoát và thử lại. + Nhập mật khẩu của bạn + Nhớ thông tin đăng nhập của tôi trên thiết bị này + Quên mật khẩu? + Tìm hiểu thêm + Xác minh + Các tệp tin bổ sung cho %s + Đang tải xuống + \ No newline at end of file diff --git a/vending-app/src/main/res/values-zh-rCN/strings.xml b/vending-app/src/main/res/values-zh-rCN/strings.xml index 1dd87d3fed..1353d852d2 100644 --- a/vending-app/src/main/res/values-zh-rCN/strings.xml +++ b/vending-app/src/main/res/values-zh-rCN/strings.xml @@ -13,10 +13,12 @@ 了解详情 验证 许可证通知 - 当应用尝试验证其许可证但您未登录任何 Google 帐户时发出通知。 + 当应用尝试验证其许可证但您未登录任何 Google 账户时发出通知。 %1$s无法验证许可证 如果应用出现异常,请登录您购买该应用所使用的 Google 帐号。 登录 忽略 microG Companion + %s的附件文件 + 文件下载中 \ No newline at end of file diff --git a/vending-app/src/main/res/values/strings.xml b/vending-app/src/main/res/values/strings.xml index d505d6fdfd..f2683abdfc 100644 --- a/vending-app/src/main/res/values/strings.xml +++ b/vending-app/src/main/res/values/strings.xml @@ -28,4 +28,8 @@ Forget password? Learn more Verify + + Additional files for %s + Downloading +