From e90ce370d4d4a80070c025c037df7bda1355feec Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 6 Oct 2021 10:22:29 +0200 Subject: [PATCH 01/63] Don't log callbacks of Google Sign-In page to logcat Google seems to include sensitive account details in some of these fields. Fixes #1567 --- .../org/microg/gms/auth/login/LoginActivity.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 94706145..d139f244 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 @@ -389,7 +389,7 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final void addAccount(String json) { - Log.d(TAG, "JSBridge: addAccount " + json); + Log.d(TAG, "JSBridge: addAccount"); } @JavascriptInterface @@ -425,7 +425,7 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final String getAndroidId() { long androidId = LastCheckinInfo.read(LoginActivity.this).getAndroidId(); - Log.d(TAG, "JSBridge: getAndroidId " + androidId); + Log.d(TAG, "JSBridge: getAndroidId"); if (androidId == 0 || androidId == -1) return null; return Long.toHexString(androidId); } @@ -442,7 +442,7 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final void getDroidGuardResult(String s) { - Log.d(TAG, "JSBridge: getDroidGuardResult: " + s); + Log.d(TAG, "JSBridge: getDroidGuardResult"); } @JavascriptInterface @@ -492,7 +492,6 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final void hideKeyboard() { - Log.d(TAG, "JSBridge: hideKeyboard"); inputMethodManager.hideSoftInputFromWindow(webView.getWindowToken(), 0); } @@ -508,7 +507,7 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final void log(String s) { - Log.d(TAG, "JSBridge: log " + s); + Log.d(TAG, "JSBridge: log"); } @JavascriptInterface @@ -518,13 +517,12 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final void setAccountIdentifier(String accountIdentifier) { - Log.d(TAG, "JSBridge: setAccountIdentifier " + accountIdentifier); + Log.d(TAG, "JSBridge: setAccountIdentifier"); } @TargetApi(HONEYCOMB) @JavascriptInterface public final void setBackButtonEnabled(boolean backButtonEnabled) { - Log.d(TAG, "JSBridge: setBackButtonEnabled: " + backButtonEnabled); if (SDK_INT <= GINGERBREAD_MR1) return; int visibility = getWindow().getDecorView().getSystemUiVisibility(); if (backButtonEnabled) @@ -542,13 +540,11 @@ public class LoginActivity extends AssistantActivity { @JavascriptInterface public final void showKeyboard() { - Log.d(TAG, "JSBridge: showKeyboard"); inputMethodManager.showSoftInput(webView, SHOW_IMPLICIT); } @JavascriptInterface public final void showView() { - Log.d(TAG, "JSBridge: showView"); runOnUiThread(() -> webView.setVisibility(VISIBLE)); } From 82d11e4e7a31bfae131fbd4487b3ca456d31fdaf Mon Sep 17 00:00:00 2001 From: Francesco Saltori Date: Tue, 24 Aug 2021 18:59:36 +0200 Subject: [PATCH 02/63] Log which app was denied push permission --- .../src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt index 3f228476..f1ba3435 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -70,10 +70,10 @@ private suspend fun ensureAppRegistrationAllowed(context: Context, database: Gcm context.startActivity(i) } if (!accepted) { - throw RuntimeException("Push permission not granted to app") + throw RuntimeException("Push permission not granted to $packageName") } } else if (app?.allowRegister == false) { - throw RuntimeException("Push permission not granted to app") + throw RuntimeException("Push permission not granted to $packageName") } } From ae8516a3397eb5d2c3edaef1330c0fec641ae142 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 29 Oct 2021 15:29:52 +0200 Subject: [PATCH 03/63] Add some Cast APIs --- play-services-base-api/build.gradle | 1 + .../google/android/gms/cast/RequestItem.aidl | 8 +++++++ .../gms/cast/internal/IBundleCallback.aidl | 10 +++++++++ .../gms/cast/internal/ICastService.aidl | 18 +++++++++++++++ .../google/android/gms/cast/RequestItem.java | 22 +++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 play-services-cast-api/src/main/aidl/com/google/android/gms/cast/RequestItem.aidl create mode 100644 play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/IBundleCallback.aidl create mode 100644 play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/ICastService.aidl create mode 100644 play-services-cast-api/src/main/java/com/google/android/gms/cast/RequestItem.java diff --git a/play-services-base-api/build.gradle b/play-services-base-api/build.gradle index ac43ddc9..572ea274 100644 --- a/play-services-base-api/build.gradle +++ b/play-services-base-api/build.gradle @@ -28,6 +28,7 @@ android { aidlPackageWhiteList "com/google/android/gms/common/data/DataHolder.aidl" aidlPackageWhiteList "com/google/android/gms/common/images/WebImage.aidl" + aidlPackageWhiteList "com/google/android/gms/common/api/internal/IStatusCallback.aidl" defaultConfig { versionName version diff --git a/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/RequestItem.aidl b/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/RequestItem.aidl new file mode 100644 index 00000000..96c3751e --- /dev/null +++ b/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/RequestItem.aidl @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.cast; + +parcelable RequestItem; diff --git a/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/IBundleCallback.aidl b/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/IBundleCallback.aidl new file mode 100644 index 00000000..15043379 --- /dev/null +++ b/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/IBundleCallback.aidl @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.cast.internal; + +interface IBundleCallback { + oneway void onBundle(in Bundle bundle); +} diff --git a/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/ICastService.aidl b/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/ICastService.aidl new file mode 100644 index 00000000..7ee5c972 --- /dev/null +++ b/play-services-cast-api/src/main/aidl/com/google/android/gms/cast/internal/ICastService.aidl @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.cast.internal; + +import com.google.android.gms.common.api.internal.IStatusCallback; +import com.google.android.gms.cast.internal.IBundleCallback; +import com.google.android.gms.cast.RequestItem; + +interface ICastService { + oneway void broadcastPrecacheMessageLegacy(IStatusCallback callback, in String[] arg2, String precacheData) = 0; + oneway void broadcastPrecacheMessage(IStatusCallback callback, in String[] arg2, String precacheData, in List requestItems) = 1; + oneway void getCxLessStatus(IStatusCallback callback) = 3; + oneway void getFeatureFlags(IBundleCallback callback, in String[] flags) = 4; + oneway void getCastStatusCodeDictionary(IBundleCallback callback, in String[] dictionaries) = 5; +} diff --git a/play-services-cast-api/src/main/java/com/google/android/gms/cast/RequestItem.java b/play-services-cast-api/src/main/java/com/google/android/gms/cast/RequestItem.java new file mode 100644 index 00000000..2d38c132 --- /dev/null +++ b/play-services-cast-api/src/main/java/com/google/android/gms/cast/RequestItem.java @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.cast; + +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; + +public class RequestItem extends AutoSafeParcelable { + @Field(2) + public String url; + @Field(3) + public int protocolType; + @Field(4) + public int initialTime; + @Field(5) + public String hlsSegmentFormat; + + public static Creator CREATOR = new AutoCreator(RequestItem.class); +} From a808476b7df4b652fe955e3f46437f1d89a8eb0c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 13 Oct 2021 14:44:01 -0300 Subject: [PATCH 04/63] Work around rare crashes that can happen when fragments are not attached to a context while co-routines are still executing but requiring a context Change-Id: Ie6c7cee50014b59c25384d3bf9a122081b9917fc --- .../gms/ui/DeviceRegistrationFragment.kt | 8 +++++--- .../DeviceRegistrationPreferencesFragment.kt | 3 ++- .../gms/ui/PushNotificationAdvancedFragment.kt | 18 ++++++++++++------ .../gms/ui/PushNotificationAllAppsFragment.kt | 2 +- .../gms/ui/PushNotificationAppFragment.kt | 5 +++-- .../microg/gms/ui/PushNotificationFragment.kt | 10 ++++++---- .../org/microg/gms/ui/SafetyNetFragment.kt | 10 ++++++---- .../org/microg/gms/ui/SettingsFragment.kt | 8 +++++--- .../ui/ExposureNotificationsAppFragment.kt | 5 +++-- .../core/ui/ExposureNotificationsFragment.kt | 8 +++++--- ...ExposureNotificationsPreferencesFragment.kt | 11 ++++++----- .../ui/ExposureNotificationsRpisFragment.kt | 3 ++- 12 files changed, 56 insertions(+), 35 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt index 4a59ffb4..88938f7b 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationFragment.kt @@ -31,10 +31,11 @@ class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragmen } fun setEnabled(newStatus: Boolean) { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val info = getCheckinServiceInfo(requireContext()) + val info = getCheckinServiceInfo(appContext) val newConfiguration = info.configuration.copy(enabled = newStatus) - setCheckinServiceConfiguration(requireContext(), newConfiguration) + setCheckinServiceConfiguration(appContext, newConfiguration) displayServiceInfo(info.copy(configuration = newConfiguration)) } } @@ -45,8 +46,9 @@ class DeviceRegistrationFragment : Fragment(R.layout.device_registration_fragmen override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - displayServiceInfo(getCheckinServiceInfo(requireContext())) + displayServiceInfo(getCheckinServiceInfo(appContext)) } } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt index a9aa42ed..6d27eed6 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt @@ -44,8 +44,9 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { private fun updateStatus() { handler.postDelayed(updateRunnable, UPDATE_INTERVAL) + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val serviceInfo = getCheckinServiceInfo(requireContext()) + val serviceInfo = getCheckinServiceInfo(appContext) statusCategory.isVisible = serviceInfo.configuration.enabled if (serviceInfo.lastCheckin > 0) { status.summary = getString(R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0)) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt index f248e97b..80fe48d7 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAdvancedFragment.kt @@ -32,45 +32,50 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { networkOther = preferenceScreen.findPreference(GcmPrefs.PREF_NETWORK_OTHER) ?: networkOther confirmNewApps.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { if (newValue is Boolean) { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(requireContext()).configuration.copy(confirmNewApps = newValue)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(confirmNewApps = newValue)) } updateContent() } true } networkMobile.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(requireContext()).configuration.copy(mobile = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(mobile = it)) } updateContent() } true } networkWifi.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(requireContext()).configuration.copy(wifi = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(wifi = it)) } updateContent() } true } networkRoaming.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(requireContext()).configuration.copy(roaming = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(roaming = it)) } updateContent() } true } networkOther.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { (newValue as? String)?.toIntOrNull()?.let { - setGcmServiceConfiguration(requireContext(), getGcmServiceInfo(requireContext()).configuration.copy(other = it)) + setGcmServiceConfiguration(appContext, getGcmServiceInfo(appContext).configuration.copy(other = it)) } updateContent() } @@ -84,8 +89,9 @@ class PushNotificationAdvancedFragment : PreferenceFragmentCompat() { } private fun updateContent() { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val serviceInfo = getGcmServiceInfo(requireContext()) + val serviceInfo = getGcmServiceInfo(appContext) confirmNewApps.isChecked = serviceInfo.configuration.confirmNewApps networkMobile.value = serviceInfo.configuration.mobile.toString() networkMobile.summary = getSummaryString(serviceInfo.configuration.mobile, serviceInfo.learntMobileInterval) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt index 94095117..91cac572 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt @@ -52,8 +52,8 @@ class PushNotificationAllAppsFragment : PreferenceFragmentCompat() { } private fun updateContent() { + val context = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val context = requireContext() val apps = withContext(Dispatchers.IO) { val res = database.appList.map { app -> app to context.packageManager.getApplicationInfoIfExists(app.packageName) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt index 464b5806..28cd3b55 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt @@ -46,12 +46,13 @@ class PushNotificationAppFragment : Fragment(R.layout.push_notification_fragment override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val pm = requireContext().packageManager + val pm = appContext.packageManager val applicationInfo = pm.getApplicationInfoIfExists(packageName) binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName binding.appIcon = applicationInfo?.loadIcon(pm) - ?: AppCompatResources.getDrawable(requireContext(), android.R.mipmap.sym_def_app_icon) + ?: AppCompatResources.getDrawable(appContext, android.R.mipmap.sym_def_app_icon) } } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt index d0cb577e..95127689 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationFragment.kt @@ -30,10 +30,11 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { } fun setEnabled(newStatus: Boolean) { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val info = getGcmServiceInfo(requireContext()) + val info = getGcmServiceInfo(appContext) val newConfiguration = info.configuration.copy(enabled = newStatus) - setGcmServiceConfiguration(requireContext(), newConfiguration) + setGcmServiceConfiguration(appContext, newConfiguration) displayServiceInfo(info.copy(configuration = newConfiguration)) } } @@ -44,9 +45,10 @@ class PushNotificationFragment : Fragment(R.layout.push_notification_fragment) { override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - displayServiceInfo(getGcmServiceInfo(requireContext())) - binding.checkinEnabled = getCheckinServiceInfo(requireContext()).configuration.enabled + displayServiceInfo(getGcmServiceInfo(appContext)) + binding.checkinEnabled = getCheckinServiceInfo(appContext).configuration.enabled } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt index bb83826c..bae598e8 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt @@ -32,10 +32,11 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { } fun setEnabled(newStatus: Boolean) { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val info = getSafetyNetServiceInfo(requireContext()) + val info = getSafetyNetServiceInfo(appContext) val newConfiguration = info.configuration.copy(enabled = newStatus) - displayServiceInfo(setSafetyNetServiceConfiguration(requireContext(), newConfiguration)) + displayServiceInfo(setSafetyNetServiceConfiguration(appContext, newConfiguration)) } } @@ -45,9 +46,10 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - binding.checkinEnabled = getCheckinServiceInfo(requireContext()).configuration.enabled - displayServiceInfo(getSafetyNetServiceInfo(requireContext())) + binding.checkinEnabled = getCheckinServiceInfo(appContext).configuration.enabled + displayServiceInfo(getSafetyNetServiceInfo(appContext)) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 5a1d8f9a..681b1f4c 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -5,6 +5,7 @@ package org.microg.gms.ui +import android.content.Context import android.os.Bundle import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -50,12 +51,13 @@ class SettingsFragment : ResourceSettingsFragment() { override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - updateDetails() + updateDetails(appContext) } } - private suspend fun updateDetails() { + private suspend fun updateDetails(context: Context) { if (getGcmServiceInfo(requireContext()).configuration.enabled) { val database = GcmDatabase(context) val regCount = database.registrationList.size @@ -68,7 +70,7 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_CHECKIN)!!.setSummary(if (getCheckinServiceInfo(requireContext()).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(requireContext()).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - val backendCount = UnifiedLocationClient[requireContext()].getLocationBackends().size + UnifiedLocationClient[requireContext()].getGeocoderBackends().size + val backendCount = UnifiedLocationClient[context].getLocationBackends().size + UnifiedLocationClient[requireContext()].getGeocoderBackends().size findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount); findPreference(PREF_EXPOSURE)?.isVisible = NearbyPreferencesIntegration.isAvailable diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt index 7d8a36fc..15e95ad0 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt @@ -40,12 +40,13 @@ class ExposureNotificationsAppFragment : Fragment(R.layout.exposure_notification override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val pm = requireContext().packageManager + val pm = appContext.packageManager val applicationInfo = pm.getApplicationInfoIfExists(packageName) binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName binding.appIcon = applicationInfo?.loadIcon(pm) - ?: AppCompatResources.getDrawable(requireContext(), android.R.mipmap.sym_def_app_icon) + ?: AppCompatResources.getDrawable(appContext, android.R.mipmap.sym_def_app_icon) } } } diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt index 6cc00f5e..550ccba2 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsFragment.kt @@ -32,10 +32,11 @@ class ExposureNotificationsFragment : Fragment(R.layout.exposure_notifications_f } fun setEnabled(newStatus: Boolean) { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val info = getExposureNotificationsServiceInfo(requireContext()) + val info = getExposureNotificationsServiceInfo(appContext) val newConfiguration = info.configuration.copy(enabled = newStatus) - setExposureNotificationsServiceConfiguration(requireContext(), newConfiguration) + setExposureNotificationsServiceConfiguration(appContext, newConfiguration) displayServiceInfo(info.copy(configuration = newConfiguration)) } } @@ -46,8 +47,9 @@ class ExposureNotificationsFragment : Fragment(R.layout.exposure_notifications_f override fun onResume() { super.onResume() + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - displayServiceInfo(getExposureNotificationsServiceInfo(requireContext())) + displayServiceInfo(getExposureNotificationsServiceInfo(appContext)) } } } diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt index c7ae6332..df0731ff 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt @@ -101,15 +101,16 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { } private fun updateStatus() { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { handler.postDelayed(updateStatusRunnable, UPDATE_STATUS_INTERVAL) - val enabled = getExposureNotificationsServiceInfo(requireContext()).configuration.enabled + val enabled = getExposureNotificationsServiceInfo(appContext).configuration.enabled exposureEnableInfo.isVisible = !enabled - val bluetoothSupported = ScannerService.isSupported(requireContext()) - val advertisingSupported = if (bluetoothSupported == true) AdvertiserService.isSupported(requireContext()) else bluetoothSupported + val bluetoothSupported = ScannerService.isSupported(appContext) + val advertisingSupported = if (bluetoothSupported == true) AdvertiserService.isSupported(appContext) else bluetoothSupported - exposureLocationOff.isVisible = enabled && bluetoothSupported != false && !LocationManagerCompat.isLocationEnabled(requireContext().getSystemService(LOCATION_SERVICE) as LocationManager) + exposureLocationOff.isVisible = enabled && bluetoothSupported != false && !LocationManagerCompat.isLocationEnabled(appContext.getSystemService(LOCATION_SERVICE) as LocationManager) exposureBluetoothOff.isVisible = enabled && bluetoothSupported == null && !turningBluetoothOn exposureBluetoothUnsupported.isVisible = enabled && bluetoothSupported == false exposureBluetoothNoAdvertisement.isVisible = enabled && bluetoothSupported == true && advertisingSupported != true @@ -119,9 +120,9 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { } private fun updateContent() { + val context = requireContext().applicationContext lifecycleScope.launchWhenResumed { handler.postDelayed(updateContentRunnable, UPDATE_CONTENT_INTERVAL) - val context = requireContext() val (apps, lastHourKeys, currentId) = ExposureDatabase.with(context) { database -> val apps = database.appList.map { packageName -> context.packageManager.getApplicationInfoIfExists(packageName) diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt index f6fc8d83..31564ce7 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsRpisFragment.kt @@ -72,8 +72,9 @@ class ExposureNotificationsRpisFragment : PreferenceFragmentCompat() { } fun updateChart() { + val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val rpiHourHistogram = ExposureDatabase.with(requireContext()) { database -> database.rpiHourHistogram } + val rpiHourHistogram = ExposureDatabase.with(appContext) { database -> database.rpiHourHistogram } val totalRpiCount = rpiHourHistogram.map { it.rpis }.sum() deleteAll.isEnabled = totalRpiCount > 0 histogramCategory.title = getString(R.string.prefcat_exposure_rpis_histogram_title, totalRpiCount) From a4a20dc87a7227049b7afedee39fa9bb58895c83 Mon Sep 17 00:00:00 2001 From: Marcos Marado Date: Mon, 18 Oct 2021 16:09:11 +0100 Subject: [PATCH 05/63] do not hardcode the apk path The apk path is hardcoded, but it is in fact placed inside the build path. This patch changes it, so the apk path is calculated from the build path. This patch does not change any behavior. However, it allows future changes to build path to automaticly reflect on the apk path. --- Android.mk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Android.mk b/Android.mk index 5416ad10..5cd01698 100644 --- a/Android.mk +++ b/Android.mk @@ -22,19 +22,19 @@ LOCAL_PACKAGE_NAME := GmsCore gmscore_root := $(LOCAL_PATH) gmscore_dir := play-services-core gmscore_out := $(TARGET_COMMON_OUT_ROOT)/obj/APPS/$(LOCAL_MODULE)_intermediates -gmscore_build := $(gmscore_root)/$(gmscore_dir)/build -gmscore_apk := build/outputs/apk/release/play-services-core-release-unsigned.apk +gmscore_build := $(gmscore_dir)/build +gmscore_apk := $(gmscore_build)/outputs/apk/release/play-services-core-release-unsigned.apk -$(gmscore_root)/$(gmscore_dir)/$(gmscore_apk): - rm -Rf $(gmscore_build) +$(gmscore_root)/$(gmscore_apk): + rm -Rf $(gmscore_root)/$(gmscore_build) mkdir -p $(ANDROID_BUILD_TOP)/$(gmscore_out) - ln -s $(ANDROID_BUILD_TOP)/$(gmscore_out) $(ANDROID_BUILD_TOP)/$(gmscore_build) + ln -s $(ANDROID_BUILD_TOP)/$(gmscore_out) $(ANDROID_BUILD_TOP)/$(gmscore_root)/$(gmscore_build) echo "sdk.dir=$(ANDROID_HOME)" > $(gmscore_root)/local.properties cd $(gmscore_root) && git submodule update --recursive --init cd $(gmscore_root)/$(gmscore_dir) && JAVA_TOOL_OPTIONS="$(JAVA_TOOL_OPTIONS) -Dfile.encoding=UTF8" ../gradlew assembleRelease LOCAL_CERTIFICATE := platform -LOCAL_SRC_FILES := $(gmscore_dir)/$(gmscore_apk) +LOCAL_SRC_FILES := $(gmscore_apk) LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) From 925ce2ddd940cdd0c233ba23ae985305b2bc4441 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 15 Oct 2021 16:03:41 +0200 Subject: [PATCH 06/63] Fix UI crash in Asus 5.0 devices Closes: #267 , #463 . This is the same fix made by emv412 here: https://github.com/microg/android_external_MicroGUiTools/pull/11 --- .../microg-ui-tools/src/main/res/layout/app_bar.xml | 3 +-- .../microg-ui-tools/src/main/res/layout/switch_bar.xml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/play-services-core/microg-ui-tools/src/main/res/layout/app_bar.xml b/play-services-core/microg-ui-tools/src/main/res/layout/app_bar.xml index 02d68ae3..f2b071a6 100644 --- a/play-services-core/microg-ui-tools/src/main/res/layout/app_bar.xml +++ b/play-services-core/microg-ui-tools/src/main/res/layout/app_bar.xml @@ -44,8 +44,7 @@ android:layout_marginStart="72dp" android:gravity="start" android:textAlignment="viewStart" - android:textColor="?android:attr/textColorPrimaryInverse" - android:theme="@style/TextAppearance.AppCompat.Title.Inverse"/> + android:textColor="?android:attr/textColorPrimaryInverse"/> diff --git a/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml b/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml index f8a6d55e..224fdf66 100644 --- a/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml +++ b/play-services-core/microg-ui-tools/src/main/res/layout/switch_bar.xml @@ -29,7 +29,7 @@ android:ellipsize="end" android:maxLines="2" android:text="@string/v7_preference_on" - android:theme="@style/TextAppearance.AppCompat.Title.Inverse"/> + android:textAppearance="@style/TextAppearance.AppCompat.Title.Inverse"/> Date: Tue, 9 Feb 2021 15:49:21 +0100 Subject: [PATCH 07/63] Verifiy signature of received keyfiles --- .../ExposureNotificationServiceImpl.kt | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index adec6016..9bfdfed7 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -11,6 +11,7 @@ import android.bluetooth.BluetoothAdapter import android.content.* import android.location.LocationManager import android.os.* +import android.util.Base64 import android.util.Log import androidx.core.location.LocationManagerCompat import androidx.lifecycle.Lifecycle @@ -26,12 +27,17 @@ import org.json.JSONArray import org.json.JSONObject import org.microg.gms.common.PackageUtils import org.microg.gms.nearby.exposurenotification.Constants.* +import org.microg.gms.nearby.exposurenotification.proto.TEKSignatureList import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyExport import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto import org.microg.gms.utils.warnOnTransactionIssues import java.io.File import java.io.InputStream +import java.security.KeyFactory import java.security.MessageDigest +import java.security.Signature +import java.security.spec.X509EncodedKeySpec +import java.util.zip.ZipEntry import java.util.zip.ZipFile import kotlin.math.max import kotlin.math.roundToInt @@ -39,6 +45,25 @@ import kotlin.random.Random class ExposureNotificationServiceImpl(private val context: Context, private val lifecycle: Lifecycle, private val packageName: String) : INearbyExposureNotificationService.Stub(), LifecycleOwner { + // Table of back-end public keys, used to verify the signature of the diagnosed TEKs. + // The table is indexed by package names. + private val backendPubKeyForPackage = mapOf( + Pair("ch.admin.bag.dp3t.dev", + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ=="), + Pair("ch.admin.bag.dp3t.test", + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ=="), + Pair("ch.admin.bag.dp3t.abnahme", + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ=="), + Pair("ch.admin.bag.dp3t", + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK2k9nZ8guo7JP2ELPQXnUkqDyjjJmYmpt9Zy0HPsiGXCdI3SFmLr204KNzkuITppNV5P7+bXRxiiY04NMrEITg=="), + ) + // Table of supported signature algorithms for the diagnosed TEKs. + // The table is indexed by ASN.1 OIDs as specified in https://tools.ietf.org/html/rfc5758#section-3.2 + private val sigAlgoForOid = mapOf( + Pair("1.2.840.10045.4.3.2", Signature.getInstance("SHA256withECDSA")), + Pair("1.2.840.10045.4.3.4", Signature.getInstance("SHA512withECDSA")), + ) + private fun LifecycleCoroutineScope.launchSafely(block: suspend CoroutineScope.() -> Unit): Job = launchWhenStarted { try { block() } catch (e: Exception) { Log.w(TAG, "Error in coroutine", e) } } override fun getLifecycle(): Lifecycle = lifecycle @@ -333,6 +358,10 @@ class ExposureNotificationServiceImpl(private val context: Context, private val var newKeys = if (params.keys != null) database.finishSingleMatching(tid) else 0 for ((cacheFile, hash) in todoKeyFiles) { withContext(Dispatchers.IO) { + if (!verifyKeyFile(cacheFile)) { + // FIXME: do something, perhaps reject according to some user setting + Log.w(TAG, "Using non-verified key file") + } try { ZipFile(cacheFile).use { zip -> for (entry in zip.entries()) { @@ -404,6 +433,64 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } } + private fun verifyKeyFile(file: File): Boolean { + try { + val publicKeyData = backendPubKeyForPackage.get(packageName) ?: throw Exception("Public key for ${packageName} is not available") + val publicKeyBytes: ByteArray = Base64.decode(publicKeyData, Base64.DEFAULT) + val publicKey = KeyFactory.getInstance("EC").generatePublic(X509EncodedKeySpec(publicKeyBytes)) + + ZipFile(file).use { zip -> + var dataEntry: ZipEntry? = null + var sigEntry: ZipEntry? = null + + for (entry in zip.entries()) { + when (entry.name) { + "export.bin" -> dataEntry = entry + "export.sig" -> sigEntry = entry + else -> throw Exception("Unexpected entry in zip archive: ${entry.name}") + } + } + when { + dataEntry == null -> throw Exception("Zip archive does not contain 'export.bin'") + sigEntry == null -> throw Exception("Zip archive does not contain 'export.sin'") + } + + val sigStream = zip.getInputStream(sigEntry) + val sigList = TEKSignatureList.ADAPTER.decode(sigStream) + + for (sig in sigList.signatures) { + Log.d(TAG, "Verifying signature ${sig.batch_num}/${sig.batch_size}") + val sigInfo = sig.signature_info ?: throw Exception("Signature information is missing") + Log.d(TAG, "Signature info: algo=${sigInfo.signature_algorithm} key={id=${sigInfo.verification_key_id}, version=${sigInfo.verification_key_version}}") + + val signature = sig.signature?.toByteArray() ?: throw Exception("Signature contents is missing") + val sigVerifier = sigAlgoForOid.get(sigInfo.signature_algorithm) ?: throw Exception("Signature algorithm not supported: ${sigInfo.signature_algorithm}") + sigVerifier.initVerify(publicKey) + + val stream = zip.getInputStream(dataEntry) + val buf = ByteArray(1024) + var nbRead = 0 + while (nbRead != -1) { + nbRead = stream.read(buf) + if (nbRead > 0) { + sigVerifier.update(buf, 0, nbRead) + } + } + + if (!sigVerifier.verify(signature)) { + throw Exception("Signature does not verify") + } + } + } + } catch (e: Exception) { + Log.w(TAG, "Key file verification failed: " + e.message) + return false + } + + Log.i(TAG, "Key file verification successful") + return true + } + override fun getExposureSummary(params: GetExposureSummaryParams) { lifecycleScope.launchSafely { val response = buildExposureSummary(params.token) From 8a1ac35c4d9d0d31cefd236f5da469156ba626ef Mon Sep 17 00:00:00 2001 From: Christian Grigis Date: Tue, 9 Feb 2021 18:39:34 +0100 Subject: [PATCH 08/63] Ensure zip keyfile has unique entries --- .../ExposureNotificationServiceImpl.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 9bfdfed7..86811c4b 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -445,8 +445,18 @@ class ExposureNotificationServiceImpl(private val context: Context, private val for (entry in zip.entries()) { when (entry.name) { - "export.bin" -> dataEntry = entry - "export.sig" -> sigEntry = entry + "export.bin" -> + if (dataEntry != null) { + throw Exception("Zip archive contains more than one 'export.bin' entry") + } else { + dataEntry = entry + } + "export.sig" -> + if (sigEntry != null) { + throw Exception("Zip archive contains more than one 'export.sig' entry") + } else { + sigEntry = entry + } else -> throw Exception("Unexpected entry in zip archive: ${entry.name}") } } From dd617e27b6e3c9da141a04068f8537910e45131f Mon Sep 17 00:00:00 2001 From: Christian Grigis Date: Tue, 9 Feb 2021 18:42:21 +0100 Subject: [PATCH 09/63] Verify keyfiles only when a backend PK exists --- .../ExposureNotificationServiceImpl.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 86811c4b..ca6870bb 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -57,6 +57,17 @@ class ExposureNotificationServiceImpl(private val context: Context, private val Pair("ch.admin.bag.dp3t", "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK2k9nZ8guo7JP2ELPQXnUkqDyjjJmYmpt9Zy0HPsiGXCdI3SFmLr204KNzkuITppNV5P7+bXRxiiY04NMrEITg=="), ) + + // Back-end public key for this package + private val backendPublicKey = backendPubKeyForPackage[packageName]?.let { + try { + KeyFactory.getInstance("EC").generatePublic(X509EncodedKeySpec(Base64.decode(it, Base64.DEFAULT))) + } catch (e: Exception) { + Log.w(TAG, "Failed to retrieve back-end public key for ${packageName}: " + e.message) + null + } + } + // Table of supported signature algorithms for the diagnosed TEKs. // The table is indexed by ASN.1 OIDs as specified in https://tools.ietf.org/html/rfc5758#section-3.2 private val sigAlgoForOid = mapOf( @@ -358,9 +369,9 @@ class ExposureNotificationServiceImpl(private val context: Context, private val var newKeys = if (params.keys != null) database.finishSingleMatching(tid) else 0 for ((cacheFile, hash) in todoKeyFiles) { withContext(Dispatchers.IO) { - if (!verifyKeyFile(cacheFile)) { - // FIXME: do something, perhaps reject according to some user setting - Log.w(TAG, "Using non-verified key file") + if (backendPublicKey != null && !verifyKeyFile(cacheFile)) { + Log.w(TAG, "Skipping non-verified key file") + return@withContext } try { ZipFile(cacheFile).use { zip -> @@ -435,10 +446,6 @@ class ExposureNotificationServiceImpl(private val context: Context, private val private fun verifyKeyFile(file: File): Boolean { try { - val publicKeyData = backendPubKeyForPackage.get(packageName) ?: throw Exception("Public key for ${packageName} is not available") - val publicKeyBytes: ByteArray = Base64.decode(publicKeyData, Base64.DEFAULT) - val publicKey = KeyFactory.getInstance("EC").generatePublic(X509EncodedKeySpec(publicKeyBytes)) - ZipFile(file).use { zip -> var dataEntry: ZipEntry? = null var sigEntry: ZipEntry? = null @@ -475,7 +482,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val val signature = sig.signature?.toByteArray() ?: throw Exception("Signature contents is missing") val sigVerifier = sigAlgoForOid.get(sigInfo.signature_algorithm) ?: throw Exception("Signature algorithm not supported: ${sigInfo.signature_algorithm}") - sigVerifier.initVerify(publicKey) + sigVerifier.initVerify(backendPublicKey) val stream = zip.getInputStream(dataEntry) val buf = ByteArray(1024) From 9067f66f0c524a67d13a06d4e801e9b19c4def2a Mon Sep 17 00:00:00 2001 From: Christian Grigis Date: Tue, 2 Nov 2021 14:52:04 +0100 Subject: [PATCH 10/63] Instantiate key signatures on demand --- .../ExposureNotificationServiceImpl.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index ca6870bb..5aed2a2a 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -48,14 +48,14 @@ class ExposureNotificationServiceImpl(private val context: Context, private val // Table of back-end public keys, used to verify the signature of the diagnosed TEKs. // The table is indexed by package names. private val backendPubKeyForPackage = mapOf( - Pair("ch.admin.bag.dp3t.dev", - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ=="), - Pair("ch.admin.bag.dp3t.test", - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ=="), - Pair("ch.admin.bag.dp3t.abnahme", - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ=="), - Pair("ch.admin.bag.dp3t", - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK2k9nZ8guo7JP2ELPQXnUkqDyjjJmYmpt9Zy0HPsiGXCdI3SFmLr204KNzkuITppNV5P7+bXRxiiY04NMrEITg=="), + "ch.admin.bag.dp3t.dev" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ==", + "ch.admin.bag.dp3t.test" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ==", + "ch.admin.bag.dp3t.abnahme" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ==", + "ch.admin.bag.dp3t" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK2k9nZ8guo7JP2ELPQXnUkqDyjjJmYmpt9Zy0HPsiGXCdI3SFmLr204KNzkuITppNV5P7+bXRxiiY04NMrEITg==", ) // Back-end public key for this package @@ -70,9 +70,9 @@ class ExposureNotificationServiceImpl(private val context: Context, private val // Table of supported signature algorithms for the diagnosed TEKs. // The table is indexed by ASN.1 OIDs as specified in https://tools.ietf.org/html/rfc5758#section-3.2 - private val sigAlgoForOid = mapOf( - Pair("1.2.840.10045.4.3.2", Signature.getInstance("SHA256withECDSA")), - Pair("1.2.840.10045.4.3.4", Signature.getInstance("SHA512withECDSA")), + private val sigAlgoForOid = mapOf>( + "1.2.840.10045.4.3.2" to { Signature.getInstance("SHA256withECDSA") }, + "1.2.840.10045.4.3.4" to { Signature.getInstance("SHA512withECDSA") }, ) private fun LifecycleCoroutineScope.launchSafely(block: suspend CoroutineScope.() -> Unit): Job = launchWhenStarted { try { block() } catch (e: Exception) { Log.w(TAG, "Error in coroutine", e) } } @@ -481,7 +481,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val Log.d(TAG, "Signature info: algo=${sigInfo.signature_algorithm} key={id=${sigInfo.verification_key_id}, version=${sigInfo.verification_key_version}}") val signature = sig.signature?.toByteArray() ?: throw Exception("Signature contents is missing") - val sigVerifier = sigAlgoForOid.get(sigInfo.signature_algorithm) ?: throw Exception("Signature algorithm not supported: ${sigInfo.signature_algorithm}") + val sigVerifier = (sigAlgoForOid.get(sigInfo.signature_algorithm) ?: throw Exception("Signature algorithm not supported: ${sigInfo.signature_algorithm}"))() sigVerifier.initVerify(backendPublicKey) val stream = zip.getInputStream(dataEntry) From d25e351c41bd43d6b755e50a09637f38bff5b063 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 28 May 2021 11:40:08 -0300 Subject: [PATCH 11/63] Re-connected in SettingsFragment if we are erroneously disconnected --- .../org/microg/gms/ui/SettingsFragment.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 681b1f4c..769e4cba 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -5,6 +5,7 @@ package org.microg.gms.ui +import android.content.Intent import android.content.Context import android.os.Bundle import androidx.lifecycle.lifecycleScope @@ -13,6 +14,8 @@ import androidx.preference.Preference import com.google.android.gms.R import org.microg.gms.checkin.getCheckinServiceInfo import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.McsConstants.ACTION_RECONNECT +import org.microg.gms.gcm.TriggerReceiver import org.microg.gms.gcm.getGcmServiceInfo import org.microg.gms.safetynet.getSafetyNetServiceInfo import org.microg.nlp.client.UnifiedLocationClient @@ -58,24 +61,29 @@ class SettingsFragment : ResourceSettingsFragment() { } private suspend fun updateDetails(context: Context) { - if (getGcmServiceInfo(requireContext()).configuration.enabled) { + val gcmServiceInfo = getGcmServiceInfo(context) + if (gcmServiceInfo.configuration.enabled) { val database = GcmDatabase(context) val regCount = database.registrationList.size + // check if we are connected as we should be and re-connect if not + if (!gcmServiceInfo.connected) { + context.sendBroadcast(Intent(ACTION_RECONNECT, null, context, TriggerReceiver::class.java)) + } database.close() findPreference(PREF_GCM)!!.summary = getString(R.string.service_status_enabled_short) + " - " + resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) } else { findPreference(PREF_GCM)!!.setSummary(R.string.service_status_disabled_short) } - findPreference(PREF_CHECKIN)!!.setSummary(if (getCheckinServiceInfo(requireContext()).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(requireContext()).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_CHECKIN)!!.setSummary(if (getCheckinServiceInfo(context).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(context).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - val backendCount = UnifiedLocationClient[context].getLocationBackends().size + UnifiedLocationClient[requireContext()].getGeocoderBackends().size + val backendCount = UnifiedLocationClient[context].getLocationBackends().size + UnifiedLocationClient[context].getGeocoderBackends().size findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount); findPreference(PREF_EXPOSURE)?.isVisible = NearbyPreferencesIntegration.isAvailable - findPreference(PREF_EXPOSURE)?.icon = NearbyPreferencesIntegration.getIcon(requireContext()) - findPreference(PREF_EXPOSURE)?.summary = NearbyPreferencesIntegration.getExposurePreferenceSummary(requireContext()) + findPreference(PREF_EXPOSURE)?.icon = NearbyPreferencesIntegration.getIcon(context) + findPreference(PREF_EXPOSURE)?.summary = NearbyPreferencesIntegration.getExposurePreferenceSummary(context) } companion object { From 1367649a18a4761f3621f681f6a3eeb76d49d10b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 28 May 2021 11:43:16 -0300 Subject: [PATCH 12/63] Lower heartbeat interval when we find the connection reset when wanting to send a message --- .../src/main/java/org/microg/gms/gcm/McsService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index e5c405a4..6baa666e 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -62,6 +62,7 @@ import java.io.Closeable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.Socket; +import java.net.SocketException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -223,6 +224,7 @@ public class McsService extends Service implements Handler.Callback { @Override public void onDestroy() { + Log.d(TAG, "onDestroy"); alarmManager.cancel(heartbeatIntent); closeAll(); database.close(); @@ -242,7 +244,7 @@ public class McsService extends Service implements Handler.Callback { } // consider connection to be dead if we did not receive an ack within twice the heartbeat interval int heartbeatMs = GcmPrefs.get(context).getHeartbeatMsFor(activeNetworkPref); - if (heartbeatMs < 0) { + if (heartbeatMs < 0) { // TODO how can this be negative? closeAll(); } else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * heartbeatMs) { logd(null, "No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds"); @@ -633,6 +635,12 @@ public class McsService extends Service implements Handler.Callback { case MSG_INPUT_ERROR: case MSG_OUTPUT_ERROR: logd(this, "I/O error: " + msg.obj); + if (msg.obj instanceof SocketException) { + SocketException e = (SocketException) msg.obj; + if ("Connection reset".equals(e.getMessage())) { + GcmPrefs.get(this).learnTimeout(this, activeNetworkPref); + } + } rootHandler.sendMessage(rootHandler.obtainMessage(MSG_TEARDOWN, msg.obj)); return true; case MSG_TEARDOWN: From f806e1bcdb709452c75d070f5381bead2e6c7db3 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 3 Jun 2021 15:26:28 -0300 Subject: [PATCH 13/63] When check-in fails due to missing internet access, try again as soon as access is restored --- .../org/microg/gms/checkin/TriggerReceiver.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java index 00a27f11..33805f00 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/TriggerReceiver.java @@ -16,16 +16,21 @@ package org.microg.gms.checkin; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.NetworkRequest; import android.util.Log; import androidx.legacy.content.WakefulBroadcastReceiver; import org.microg.gms.common.ForegroundServiceContext; +import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.checkin.CheckinService.EXTRA_FORCE_CHECKIN; import static org.microg.gms.checkin.CheckinService.REGULAR_CHECKIN_INTERVAL; @@ -50,6 +55,14 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { Intent subIntent = new Intent(context, CheckinService.class); subIntent.putExtra(EXTRA_FORCE_CHECKIN, force); startWakefulService(new ForegroundServiceContext(context), subIntent); + } else if (SDK_INT >= 23) { + // no network, register a network callback to retry when we have internet + NetworkRequest networkRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_INTERNET) + .build(); + Intent i = new Intent(context, TriggerReceiver.class); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, i, FLAG_UPDATE_CURRENT); + cm.registerNetworkCallback(networkRequest, pendingIntent); } } else { Log.d(TAG, "Ignoring " + intent + ": checkin is disabled"); From 14d666a163697a981da9bc4d362177c17ad4701b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 4 Jun 2021 11:16:58 -0300 Subject: [PATCH 14/63] Do a check-in if we are not actually checked in, but should be, e.g. cleared app data Change-Id: If800339fa35644d8783203fbd511128457d6d101 --- .../main/java/org/microg/gms/gcm/TriggerReceiver.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java index 10cc6d68..b3f84d96 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java @@ -25,6 +25,7 @@ import android.util.Log; import androidx.legacy.content.WakefulBroadcastReceiver; +import org.microg.gms.checkin.CheckinPrefs; import org.microg.gms.checkin.LastCheckinInfo; import org.microg.gms.common.ForegroundServiceContext; @@ -67,6 +68,14 @@ public class TriggerReceiver extends WakefulBroadcastReceiver { if (LastCheckinInfo.read(context).getAndroidId() == 0) { Log.d(TAG, "Ignoring " + intent + ": need to checkin first."); + if (CheckinPrefs.isEnabled(context)) { + // Do a check-in if we are not actually checked in, + // but should be, e.g. cleared app data + Log.d(TAG, "Requesting check-in..."); + String action = "android.server.checkin.CHECKIN"; + Class clazz = org.microg.gms.checkin.TriggerReceiver.class; + context.sendBroadcast(new Intent(action, null, context, clazz)); + } return; } From 00a06d4b94156c60373c5141d53f4e17772767d7 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 13 Oct 2021 13:13:19 -0300 Subject: [PATCH 15/63] Don't reconnect to gcm when opening settings This doesn't treat the underlying issues and can make debugging harder if we suddenly auto-connect due to this. Change-Id: I9c04c78e8ebda8472b0cd8c7b0b0deb2e2300139 --- .../src/main/java/org/microg/gms/gcm/McsService.java | 5 +++-- .../main/kotlin/org/microg/gms/ui/SettingsFragment.kt | 9 +-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 6baa666e..04dcfdfb 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -244,9 +244,10 @@ public class McsService extends Service implements Handler.Callback { } // consider connection to be dead if we did not receive an ack within twice the heartbeat interval int heartbeatMs = GcmPrefs.get(context).getHeartbeatMsFor(activeNetworkPref); - if (heartbeatMs < 0) { // TODO how can this be negative? + // if disabled for active network, heartbeatMs will be -1 + if (heartbeatMs < 0) { closeAll(); - } else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2 * heartbeatMs) { + } else if (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime > 2L * heartbeatMs) { logd(null, "No heartbeat for " + (SystemClock.elapsedRealtime() - lastHeartbeatAckElapsedRealtime) / 1000 + " seconds, connection assumed to be dead after " + 2 * heartbeatMs / 1000 + " seconds"); GcmPrefs.get(context).learnTimeout(context, activeNetworkPref); return false; diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 769e4cba..b05bcb16 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -5,7 +5,6 @@ package org.microg.gms.ui -import android.content.Intent import android.content.Context import android.os.Bundle import androidx.lifecycle.lifecycleScope @@ -14,8 +13,6 @@ import androidx.preference.Preference import com.google.android.gms.R import org.microg.gms.checkin.getCheckinServiceInfo import org.microg.gms.gcm.GcmDatabase -import org.microg.gms.gcm.McsConstants.ACTION_RECONNECT -import org.microg.gms.gcm.TriggerReceiver import org.microg.gms.gcm.getGcmServiceInfo import org.microg.gms.safetynet.getSafetyNetServiceInfo import org.microg.nlp.client.UnifiedLocationClient @@ -65,10 +62,6 @@ class SettingsFragment : ResourceSettingsFragment() { if (gcmServiceInfo.configuration.enabled) { val database = GcmDatabase(context) val regCount = database.registrationList.size - // check if we are connected as we should be and re-connect if not - if (!gcmServiceInfo.connected) { - context.sendBroadcast(Intent(ACTION_RECONNECT, null, context, TriggerReceiver::class.java)) - } database.close() findPreference(PREF_GCM)!!.summary = getString(R.string.service_status_enabled_short) + " - " + resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) } else { @@ -79,7 +72,7 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(context).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) val backendCount = UnifiedLocationClient[context].getLocationBackends().size + UnifiedLocationClient[context].getGeocoderBackends().size - findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount); + findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount) findPreference(PREF_EXPOSURE)?.isVisible = NearbyPreferencesIntegration.isAvailable findPreference(PREF_EXPOSURE)?.icon = NearbyPreferencesIntegration.getIcon(context) From 222b03f7eef92912263141152b62d164720b2297 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 13 Oct 2021 13:38:45 -0300 Subject: [PATCH 16/63] Respect bounds for heartbeat intervals when learning from timeouts Without this it can happen that we learn the interval too close to 0 which causes us to assume that we are always disconnected breaking Mcs network functionality Change-Id: I0dee59d1365a8e10941dd346bfcd7af19d79d523 --- .../src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt index 3a0b8b77..8982149e 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmPrefs.kt @@ -125,13 +125,16 @@ data class GcmPrefs( Log.d("GmsGcmPrefs", "learnTimeout: $pref") when (pref) { PREF_NETWORK_MOBILE, PREF_NETWORK_ROAMING -> setSettings(context, Gcm.getContentUri(context)) { - put(Gcm.LEARNT_MOBILE, (learntMobileInterval * 0.95).toInt()) + val newInterval = (learntMobileInterval * 0.95).toInt() + put(Gcm.LEARNT_MOBILE, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } PREF_NETWORK_WIFI -> setSettings(context, Gcm.getContentUri(context)) { - put(Gcm.LEARNT_WIFI, (learntWifiInterval * 0.95).toInt()) + val newInterval = (learntWifiInterval * 0.95).toInt() + put(Gcm.LEARNT_WIFI, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } else -> setSettings(context, Gcm.getContentUri(context)) { - put(Gcm.LEARNT_OTHER, (learntOtherInterval * 0.95).toInt()) + val newInterval = (learntOtherInterval * 0.95).toInt() + put(Gcm.LEARNT_OTHER, max(MIN_INTERVAL, min(newInterval, MAX_INTERVAL))) } } } From 0ae3a82b4e2bdbca577fae805a16ad6eb2dec815 Mon Sep 17 00:00:00 2001 From: Marcus Hoffmann Date: Tue, 9 Nov 2021 15:24:41 +0100 Subject: [PATCH 17/63] add CWA and CCTG exposure file public signing keys From: https://github.com/corona-warn-app/cwa-documentation/issues/740#issuecomment-963223074 --- .../ExposureNotificationServiceImpl.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 5aed2a2a..4ea375ee 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -56,6 +56,15 @@ class ExposureNotificationServiceImpl(private val context: Context, private val "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsFcEnOPY4AOAKkpv9HSdW2BrhUCWwL15Hpqu5zHaWy1Wno2KR8G6dYJ8QO0uZu1M6j8z6NGXFVZcpw7tYeXAqQ==", "ch.admin.bag.dp3t" to "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK2k9nZ8guo7JP2ELPQXnUkqDyjjJmYmpt9Zy0HPsiGXCdI3SFmLr204KNzkuITppNV5P7+bXRxiiY04NMrEITg==", + // CWA, see https://github.com/corona-warn-app/cwa-documentation/issues/740#issuecomment-963223074 + "de.rki.coronawarnapp" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==", + // CCTG uses CWA infrastucture + "de.corona.tracing" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==", + // CCTG-Test builds don't have access any staging infrastructure, so again CWA key + "de.corona.tracing.test" to + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==", ) // Back-end public key for this package From 507c20aaac386aec3ed59c525892db0c37bdc3ff Mon Sep 17 00:00:00 2001 From: Francesco Saltori Date: Thu, 25 Nov 2021 06:43:59 +0100 Subject: [PATCH 18/63] Update and improve Italian translation (#1457) --- .../src/main/res/values-it/strings.xml | 1 + .../src/main/res/values-it/strings.xml | 6 ++ .../src/main/res/values-it/strings.xml | 8 +- .../src/main/res/values-it/permissions.xml | 20 ++--- .../src/main/res/values-it/strings.xml | 73 +++++++++---------- .../src/main/res/values-it/strings.xml | 54 ++++++++------ .../src/main/res/values-it/strings.xml | 7 ++ 7 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 play-services-base-core/src/main/res/values-it/strings.xml create mode 100644 play-services-nearby-core/src/main/res/values-it/strings.xml diff --git a/play-services-base-core-ui/src/main/res/values-it/strings.xml b/play-services-base-core-ui/src/main/res/values-it/strings.xml index e506c9f8..e4aab62e 100644 --- a/play-services-base-core-ui/src/main/res/values-it/strings.xml +++ b/play-services-base-core-ui/src/main/res/values-it/strings.xml @@ -9,4 +9,5 @@ Nessuna Mostra tutte + Apri diff --git a/play-services-base-core/src/main/res/values-it/strings.xml b/play-services-base-core/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..ebd264b5 --- /dev/null +++ b/play-services-base-core/src/main/res/values-it/strings.xml @@ -0,0 +1,6 @@ + + + Esecuzione in background + %1$s è in esecuzione in background. + Disabilita le ottimizzazioni della batteria per %1$s oppure modifica le impostazioni delle notifiche per nascondere questa notifica. + \ No newline at end of file diff --git a/play-services-core/microg-ui-tools/src/main/res/values-it/strings.xml b/play-services-core/microg-ui-tools/src/main/res/values-it/strings.xml index 10bf1971..dbf5a822 100644 --- a/play-services-core/microg-ui-tools/src/main/res/values-it/strings.xml +++ b/play-services-core/microg-ui-tools/src/main/res/values-it/strings.xml @@ -25,15 +25,15 @@ Configurazione - Auto-controllo + Controllo dei problemi Verifica se il sistema è correttamente configurato per utilizzare microG. - Autorizzazione concessa - Autorizzazione a %1$s: + Autorizzazioni concesse + %1$s: Tocca qui per concedere l\'autorizzazione. Negare l\'autorizzazione può comportare il funzionamento anomalo di altre applicazioni. microG UI Demo - Sommario + Riepilogo Versione v0.1.0 Librerie incluse 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 d6670512..85c198a2 100644 --- a/play-services-core/src/main/res/values-it/permissions.xml +++ b/play-services-core/src/main/res/values-it/permissions.xml @@ -115,7 +115,7 @@ Gestisci i tuoi libri Gestisci i tuoi calendari Visualizza i tuoi calendari - Visualizza e gestisci i tuoi dati di Google CLoud Print + Visualizza e gestisci i tuoi dati di Google Cloud Print Visualizza le tue risorse del motore Google Compute Visualizza e gestisci le tue risorse del motore Google Compute Visualizza i tuoi lavori di Google Coordinate @@ -126,17 +126,17 @@ Visualizza e gestisci i resoconti di DoubleClick per inserzionisti Permette l\'accesso alla cartella dati dell\'applicazione Visualizza le tue applicazioni di Google Drive - Visualizza e gestisci i files di Google Drive aperti o creati con questa applicazione + Visualizza e gestisci i file di Google Drive aperti o creati con questa applicazione Ambito speciale utilizzato per permettere agli utenti l\'installazione di una applicazione - Visualizza i metadati per i files ed i documenti di Google Drive - Visualizza i tuoi files ed i tuoi documenti di Google Drive - Modifica il comportamento dei tuoi scripts di Google Apps Script - Visualizza e gestisci i tuoi files ed i tuoi documenti di Google Drive + Visualizza i metadati per i file ed i documenti di Google Drive + Visualizza i tuoi file ed i tuoi documenti di Google Drive + Modifica il comportamento dei tuoi script di Google Apps Script + Visualizza e gestisci i tuoi file ed i tuoi documenti di Google Drive Visualizza il tuo account Freebase Accedi a Freebase con il tuo account Gestisci le tue tabelle di Fusion Tables Visualizza le tue tabelle di Fusion Tables - Ambito per accedere ai dati di Google Play Games + Ambito per accedere ai dati di Google Play Giochi Gestisci i tuoi dati GAN Visualizza i tuoi dati GAN Messaggistica cloud per Chrome @@ -155,8 +155,8 @@ Visualizza i tuoi dati di Orkut Conoscere il tuo nome, le tue informazioni di base e la lista di persone con cui sei connesso su Google+ Conoscere chi sei su Google - Gestisci i tuoi dati per la API the Google Prediction - Visualizza i deti dei tuoi prodotti + Gestisci i tuoi dati per la API di Google Prediction + Visualizza i dati dei tuoi prodotti Gestisci la lista di siti e di domini che controlli Gestisci le verifiche del tuo nuovo sito con Google Leggi e scrivi gli accessi per la API Shopping Content @@ -164,7 +164,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 entro la 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 0d40b333..30ed69ad 100644 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -21,39 +21,31 @@ Giusto un secondo… Google - Continuando autorizzi questa applicazione e Google ad usare le tue informazioni in accordo con i rispettivi termini di servizio e politiche di riservatezza. + Continuando autorizzi questa applicazione e Google a usare le tue informazioni in accordo con i rispettivi termini di servizio e politiche di riservatezza. %1$s vorrebbe: %1$s vorrebbe utilizzare: Gestione dell\'account Google Ci dispiace… - "Un\'applicazione sul tuo dispositivo sta tentando di accedere all\'account Google. - -Se ciò era intenzionale, utilizza il pulsante Accedi per collegarti alla pagina di autenticazione di Google, in caso contrario utilizza il pulsante Cancella per tornare all\'applicazione che ha aperto questa finestra di dialogo." + 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. Questa operazione può richiedere alcuni secondi." - "Non disponi di una connessione ad internet. - -Potrebbe trattarsi di un disservizio temporaneo oppure non essere il tuo dispositivo Android fornito di connessione dati. Riprova quando connesso ad una rete cellulare o ad una rete Wi-Fi." - "C\'è stato un problema di comunicazione con i server di Google. - -Try again later." - "Il tuo dispositivo sta comunicando con i server di Google per salvare le informazioni sull\'account. - -Questa operazione può richiedere alcuni minuti." + Non disponi di una connessione a Internet.\n\nPotrebbe trattarsi di un disservizio temporaneo, oppure il tuo dispositivo Android potrebbe non essere configurato per utilizzare una connessione dati. Riprova una volta connesso a una rete cellulare o a una rete Wi-Fi. + C\'è stato un problema di comunicazione con i server di Google.\n\nRiprova più tardi. + Il tuo dispositivo sta comunicando con i server di Google per salvare le informazioni sul tuo account.\n\nQuesta operazione può richiedere alcuni minuti. Permetti Nega Autenticazione necessaria - %1$s necessita della tua autorizzazione ad accedere all\'account Google. + %1$s richiede la tua autorizzazione per accedere al tuo account Google. - Ascolta la trasmissione di stati interni - Ascolta messaggi C2DM - Invia messaggi C2DM ad altre applicazioni - Scambia messaggi e riceve notifiche di sincronizzazione dai server Google - Accesso esteso ai servizi Google - Fornisci i servizi microG - Permetti all\'applicazione di fornire i servizi microG senza l\'interazione dell\'utente + ascolto di broadcast interni di stato + ascolto di messaggi C2DM + invio di messaggi C2DM ad altre applicazioni + scambio di messaggi e ricezione di notifiche di sincronizzazione dai server Google + accesso esteso ai servizi Google + configurazione dei servizi microG + Permette all\'applicazione di configurare i servizi microG senza l\'interazione dell\'utente Registrazione del dispositivo Messaggistica cloud @@ -68,7 +60,7 @@ Questa operazione può richiedere alcuni minuti." Google Play Giochi %1$s vorrebbe utilizzare Play Giochi - Per usare Play Giochi è necessario installare l\'applicazione di Play Giochi. L\'applicazione può continuare ad essere utilizzata senza Play Giochi, ma è possibile che funzioni in modo anomalo. + Per usare Play Giochi è necessario installare l\'applicazione Google Play Giochi. L\'applicazione può continuare ad essere utilizzata senza Play Giochi, ma è possibile che funzioni in modo anomalo. Seleziona un luogo Non è ancora possibile selezionare un luogo. @@ -76,7 +68,7 @@ Questa operazione può richiedere alcuni minuti." Luoghi vicini (%1$.7f, %2$.7f) - microG: Il permesso di %1$s è mancante + microG: Il permesso di %1$s non è stato concesso Reti mobili Wi-Fi @@ -90,9 +82,9 @@ Questa operazione può richiedere alcuni minuti." Sistema Il sistema supporta la falsificazione della firma: - La tua ROM non dispone del supporto nativo per la falsificazione della firma. Puoi comunque utilizzare Xposed o altri sistemi per garantire la falsificazione della firma. Consulta la documentazione per conoscere quali ROM supportino la falsificazione della firma e su come utilizzare microG sulle ROM che non la supportano. + La tua ROM non dispone del supporto nativo per la falsificazione della firma. Puoi comunque utilizzare Xposed o altri sistemi per garantire la falsificazione della firma. Consulta la documentazione per conoscere quali ROM supportano questa funzionalità e come utilizzare microG sulle ROM che non la supportano. Il sistema concede l\'autorizzazione a falsificare la firma: - Questo indica che la ROM attuale supporta la falsificazione della firma, ma richiede azioni ulteriori per attivarla. Consulta la documentazione per conoscere quali operazioni siano necessarie. + Ciò indica che la ROM attuale supporta la falsificazione della firma, ma richiede azioni ulteriori per attivarla. Consulta la documentazione per conoscere quali operazioni siano necessarie. Il sistema falsifica la firma: Consulta la documentazione per conoscere quali operazioni siano necessarie. @@ -102,7 +94,7 @@ Questa operazione può richiedere alcuni minuti." %1$s installato: Installa l\'applicazione %1$s oppure un\'altra compatibile. Consulta la documentazione per conoscere quali applicazioni siano compatibili. %1$s ha la firma corretta: - L\'applicazione %1$s non è compatibile oppure la falsificazione della firma non è attiva per essa. Consulta la documentazione per conoscere quali applicazioni e ROM siano compatibili. + L\'applicazione %1$s non è compatibile oppure la falsificazione della sua firma non è abilitata. Consulta la documentazione per conoscere quali applicazioni e ROM siano compatibili. Ottimizzazioni della batteria ignorate: Tocca qui per disabilitare le ottimizzazioni della batteria. Non selezionare questa opzione potrebbe comportare il malfunzionamento delle applicazioni. @@ -119,7 +111,7 @@ Questa operazione può richiedere alcuni minuti." Sperimentale Ottimizzazioni della batteria abilitate - Hai abilitato il servizio di messaggistica cloud di Google, tuttavia le ottimizzazioni della batteria sono abilitate per microG. Al fine di garantire la ricezione delle notisfiche push, dovresti selezionare l\'opzione per ignorare le ottimizzazioni della batteria. + Hai abilitato il servizio di messaggistica cloud, tuttavia le ottimizzazioni della batteria sono abilitate per microG. Al fine di garantire la ricezione delle notifiche push, dovresti selezionare l\'opzione per ignorare le ottimizzazioni della batteria. Ignora ottimizzazioni Permesso mancante @@ -129,8 +121,8 @@ Questa operazione può richiedere alcuni minuti." Dai fiducia a Google per i permessi alle applicazioni Quando questa opzione è disabilitata, viene interpellato l\'utente prima che ogni richiesta di autorizzazione da parte delle applicazioni venga inviata a Google. Alcune applicazioni potrebbero fallire nell\'utilizzare l\'account Google se questa opzione è disabilitata. - Autorizza le applicazioni a visualizzare gli account - Quando questa opzione è abilitata, tutte le applicazioni su questo dispositivo saranno in grado di visualizzare gli indirizzi di posta elettronica dell\'account Google senza autorizzazione preventiva. + Consenti alle applicazioni di visualizzare gli account + Quando questa opzione è abilitata, tutte le applicazioni su questo dispositivo saranno in grado di visualizzare gli indirizzi di posta elettronica degli account Google senza autorizzazione preventiva. Registra il tuo dispositivo ai servizi Google e crea un identificativo univoco. Vengono rimossi da microG alcuni bit funzionali all\'identificazione dai dati di registrazione, oltre al nome dell\'account Google. Identificativo univoco Android @@ -144,11 +136,11 @@ Questa operazione può richiedere alcuni minuti." Account Aggiungi un account Google - Il servizio di messaggistica cloud di Google è un fornitore di notifiche push utilizzato da molte applicazioni di terze parti. Per poterlo utlizzare devi abilitare la registrazione del dispositivo. - Intervallo di aggiornamento del servizio di messaggistica cloud di Google + Il servizio di messaggistica cloud di Google è un fornitore di notifiche push utilizzato da molte applicazioni di terze parti. Per poterlo utilizzare devi abilitare la registrazione del dispositivo. + Intervallo di aggiornamento del servizio di messaggistica cloud L\'intervallo temporale, espresso in secondi, utilizzato dal sistema per contattare i server di Google. Aumentare questo intervallo ridurrà il consumo di batteria, tuttavia potrebbe causare ritardi nella ricezione delle notifiche push.\nDeprecato, verrà rimpiazzato in versioni future. - Applicazioni che utilizzano il servizio di messaggistica cloud di Google - Lista delle applicazioni attualmente registrate al servizio di messaggistica cloud di Google: + Applicazioni che utilizzano il servizio di messaggistica cloud + Lista delle applicazioni attualmente registrate al servizio di messaggistica cloud: Conferma le nuove applicazioni Chiedi conferma all\'utente prima di registrare una nuova applicazione per le notifiche push. Intervallo di ping: %1$s @@ -165,15 +157,16 @@ Questa operazione può richiedere alcuni minuti." Registrata Registrata: %1$s Annulla la registrazione di %1$s? - Alcune applicazioni non rinnovano automaticamente la registrazione e/o non forniscono un\'opzione per farlo manualmente. Queste applicazioni potrebbero non funzionare correttamente a seguito dell\'annullamento della registrazione.\nContinuare? - Hai negato ad un\'applicazione, già registrata in precedenza, di registrarsi per la ricezione delle notifiche push.\nDesideri annullare la registrazione così che non riceva più notifiche push in futuro? + Alcune applicazioni non rinnovano automaticamente la registrazione o non forniscono un\'opzione per farlo manualmente. Queste applicazioni potrebbero non funzionare correttamente a seguito dell\'annullamento della registrazione.\nVuoi continuare? + Hai negato a un\'applicazione, già registrata in precedenza, di registrarsi per la ricezione delle notifiche push.\nDesideri annullare la registrazione così che non riceva più notifiche push in futuro? Messaggi: %1$d (%2$d bytes) Disconnesso Connesso %1$s Ricevi le notifiche push + Vuoi consentire a %1$s di registrarsi per la ricezione delle notifiche push? Permetti la registrazione - Permetti all\'applicazione di registrarsi per la ricezione delle notifiche push. + Consenti all\'applicazione di registrarsi per la ricezione delle notifiche push. Avvia alla ricezione Avvia l\'applicazione in background in seguito alla ricezione delle notifiche push. Applicazioni che utilizzano le notifiche push @@ -181,7 +174,7 @@ Questa operazione può richiedere alcuni minuti." Applicazioni non registrate Reti da utilizzare per le notifiche push - SafetyNet di Google è un sistema di certificazione dispositivo che ne garantisce la sicurezza e la compatibilità con Android CTS. Alcune applicazioni utlizzano SafetyNet per ragioni di sicurezza o come prerequisito per la protezione da manomissione.\n\nUn\'implementazione libera di SafetyNet è contenuta in microG, tuttavia i server ufficiali richiedono che le richieste SafetyNet siano firmate utilizzando il sistema proprietario DroidGuard. Una versione contenitore di DroidGuard è disponibile come applicazione “microG DroidGuard Helper”. + SafetyNet di Google è un sistema di certificazione del dispositivo che ne garantisce la sicurezza e la compatibilità con Android CTS. Alcune applicazioni utilizzano SafetyNet per ragioni di sicurezza o come prerequisito per la protezione da manomissione.\n\nUn\'implementazione libera di SafetyNet è contenuta in microG, tuttavia i server ufficiali richiedono che le richieste SafetyNet siano firmate utilizzando il sistema proprietario DroidGuard. Una versione isolata di DroidGuard è disponibile all\'interno dell\'applicazione “microG DroidGuard Helper”. Permetti l\'attestazione del dispositivo Prova l\'attestazione di SafetyNet @@ -189,13 +182,13 @@ Questa operazione può richiedere alcuni minuti." Utilizza i server ufficiali Richiede un sistema senza privilegi di root e con l\'applicazione "microG DroidGuard Helper" installata Server ufficiale - Utilizza i server di terzi parti + Utilizza un server di terze parti I server di terze parti potrebbero essere in grado di rispondere alle richieste di SafetyNet senza la firma di DroidGuard Server di terze parti URL del server personalizzato - URL completo del server personalizzato rispondente alle richieste di attestazione SafetyNet + URL completo del server personalizzato che risponde alle richieste di attestazione SafetyNet Utilizza un certificato auto-firmato - Anziché inoltrare le richieste ad un server, firma localmente le risposte SafetyNet utlizzando un certificato auto-firmato. La maggior parte delle applicazioni rifiuteranno l\'uso di risposte auto-firmate. + Anziché inoltrare le richieste a un server, firma localmente le risposte SafetyNet utilizzando un certificato auto-firmato. La maggior parte delle applicazioni rifiuteranno l\'uso di risposte auto-firmate. Certificato auto-firmato diff --git a/play-services-nearby-core-ui/src/main/res/values-it/strings.xml b/play-services-nearby-core-ui/src/main/res/values-it/strings.xml index e46cb211..16a8dacc 100644 --- a/play-services-nearby-core-ui/src/main/res/values-it/strings.xml +++ b/play-services-nearby-core-ui/src/main/res/values-it/strings.xml @@ -21,41 +21,49 @@ Identificativi univoci raccolti %1$d identificativi univoci nell\'ultima ora Identificativi univoci attualmente trasmessi - Processate %1$d chiavi di diagnosi. - Nessun incontro esposto al virus riscontrato. - Riscontrati %1$d incontri esposti: + Nessuna esposizione al virus riscontrata. + Riscontrate %1$d esposizioni: %1$s, punteggio di rischio %2$d - Nota: Il punteggio di rischio è definito dall\'applicazione. Punteggi elevati possono riferirsi ad un basso rischio o viceversa. + Nota: Il punteggio di rischio è definito dall\'applicazione. Punteggi elevati possono riferirsi a un rischio basso o viceversa. Utilizzo della API negli ultimi 14 giorni %1$d chiamate a %2$s %1$d identificativi univoci raccolti - Elimina tutti gli identificativi univoci raccolti Eliminando tutti gli identificativi univoci raccolti non sarà possibile notificarti nel caso in cui uno dei tuoi contatti degli ultimi 14 giorni sia stato diagnosticato positivo al virus. Elimina comunque - "L\'API per le notifiche di esposizione al virus permette alle applicazioni di notificarti nel caso in cui tu sia stato esposto a qualcuno che ha riportato di esser stato diagnosticato positivo al virus. - -Data, durata e potenza del segnale associati ad una esposizione verranno condivisi con l\'applicazione relativa." - "Quando l\'API per le notifiche di esposizione al virus è abilitata, il tuo dispositivo raccoglie passivamente identificativi univoci (chiamati Identificatori di Prossimità a Rotazione, ovvero RPI) dai dispositivi vicini. - -Quando i possessori dei dispositivi riportano di essere stati diagnosticati positivi al virus, i loro identificativi univoci possono essere condivisi. Il tuo dispositivo verifica se tra gli identificativi univoci diagnosticati positivi al virus vi sono corrispondenze con quelli raccolti e calcola il tuo punteggio di rischio." + L\'API per le notifiche di esposizione al virus permette alle applicazioni di notificarti nel caso in cui tu sia stato esposto a qualcuno che ha riportato di essere stato diagnosticato positivo al virus.\n\nData, durata e potenza del segnale associati a una esposizione verranno condivisi con l\'applicazione relativa. + Quando l\'API per le notifiche di esposizione al virus è abilitata, il tuo dispositivo raccoglie passivamente degli identificativi univoci (chiamati Identificatori di Prossimità a Rotazione, o RPI) dai dispositivi vicini.\n\nQuando i possessori dei dispositivi riportano di essere stati diagnosticati positivi al virus, i loro identificativi univoci possono essere condivisi. Il tuo dispositivo verifica se tra gli identificativi univoci diagnosticati positivi al virus vi sono corrispondenze con quelli raccolti e calcola il tuo punteggio di rischio. Utilizza le notifiche di esposizione al virus Abilitare le notifiche di esposizione al virus? - "Il tuo dispositivo richiede l\'utilizzo del Bluetooth per raccogliere e condividere in modo sicuro gli identificativi univoci con altri dispositivi vicini. - -%1$s può notificarti nel caso in cui tu sia stato esposto a qualcuno che ha riportato di esser stato diagnosticato positivo al virus. - -Data, durata e potenza del segnale associati ad una esposizione al virus verranno condivisi con l\'applicazione." + Il tuo dispositivo richiede l\'utilizzo del Bluetooth per raccogliere e condividere in modo sicuro gli identificativi univoci con altri dispositivi vicini.\n\n%1$s può notificarti nel caso in cui tu sia stato esposto a qualcuno che ha riportato di essere stato diagnosticato positivo al virus.\n\nData, durata e potenza del segnale associati a una esposizione al virus verranno condivisi con l\'applicazione. Abilita Disabilitare le notifiche di esposizione al virus? - Dopo aver disabilitato le notifiche di esposizione al virus, non verrai più notificato nel caso in cui tu sia stato esposto a qualcuno che ha riportato di esser stato diagnosticato positivo. + Dopo aver disabilitato le notifiche di esposizione al virus, non verrai più notificato nel caso in cui tu sia stato esposto a qualcuno che ha riportato di essere stato diagnosticato positivo. Disabilita - Condividi i tuoi identificativi univoci con %1$s? - "I tuoi identificativi univoci degli ultimi 14 giorni saranno utilizzati per aiutare a notificare altri se sei stato vicino ad una potenziale esposizione al virus. - -La tua identità o il risultato del test non saranno condivisi con altre persone" + Vuoi condividere i tuoi identificativi univoci con %1$s? + I tuoi identificativi univoci degli ultimi 14 giorni saranno utilizzati per aiutare a notificare altri se sei stato potenzialmente esposto al virus.\n\nLa tua identità o il risultato del test non saranno condivisi con altre persone. Condividi - Devi concedere a %1$s il permesso di utilizzare le notifiche di esposizione al virus per garantirnee il corretto funzionamento. - Concedi + %1$s richiede ulteriori permessi. + Consenti + Elimina tutti gli identificativi univoci raccolti + Elimina + Esporta + Attiva + Attiva il Bluetooth + Il Bluetooth dev\'essere attivato. + Meno di 5 minuti + Circa %1$d minuti + esposizione ravvicinata + esposizione a distanza + %1$s, %2$s + Ultimo aggiornamento: %1$s + Esporta gli identificativi univoci raccolti per analizzarli con un\'altra app. + È necessario attivare la geolocalizzazione. + Il tuo dispositivo non è compatibile con le notifiche di esposizione al virus. + Il tuo dispositivo è compatibile soltanto parzialmente con le notifiche di esposizione al virus. Potrai essere notificato in caso di contatti a rischio, ma non sarai in grado di notificare gli altri. + Nessun dato + %1$s identificativi in un\'ora + Esposizioni riscontrate + Apri le impostazioni di geolocalizzazione diff --git a/play-services-nearby-core/src/main/res/values-it/strings.xml b/play-services-nearby-core/src/main/res/values-it/strings.xml new file mode 100644 index 00000000..4cbdc423 --- /dev/null +++ b/play-services-nearby-core/src/main/res/values-it/strings.xml @@ -0,0 +1,7 @@ + + + Notifiche di esposizione al virus non attive + Il Bluetooth dev\'essere attivato per poter ricevere le notifiche di esposizione al virus. + È necessario attivare la geolocalizzazione per poter ricevere le notifiche di esposizione al virus. + È necessario attivare il Bluetooth e la geolocalizzazione per poter ricevere le notifiche di esposizione al virus. + \ No newline at end of file From 64f12230f7421231a44e6cbf6415165f6fc87e5a Mon Sep 17 00:00:00 2001 From: Kaukov <20661820+Kaukov@users.noreply.github.com> Date: Thu, 9 Dec 2021 01:30:51 +0200 Subject: [PATCH 19/63] Bump GMS version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aa33fe5d..693d9fd5 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ def execResult(...args) { return stdout.toString().trim() } -def gmsVersion = "21.26.58" +def gmsVersion = "21.45.16" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def gitVersionBase = execResult('git', 'describe', '--tags', '--abbrev=0', '--match=v[0-9]*').substring(1) def gitCommitCount = Integer.parseInt(execResult('git', 'rev-list', '--count', "v$gitVersionBase..HEAD")) From 24d6b5aef5c677fbe9efcc56c4451f70809d5825 Mon Sep 17 00:00:00 2001 From: Oliver S Date: Mon, 10 Jan 2022 16:44:39 +0100 Subject: [PATCH 20/63] Use ENF version 1.1 to send confidence values --- .../microg/gms/nearby/exposurenotification/AdvertiserService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt index 396064ae..e279959b 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/AdvertiserService.kt @@ -36,7 +36,7 @@ import java.util.* @TargetApi(21) @ForegroundServiceInfo("Exposure Notification") class AdvertiserService : LifecycleService() { - private val version = VERSION_1_0 + private val version = VERSION_1_1 private var advertising = false private var wantStartAdvertising = false private val advertiser: BluetoothLeAdvertiser? From 7321ea7d2551c91faa119e0626a83560873a6331 Mon Sep 17 00:00:00 2001 From: Oliver S Date: Mon, 10 Jan 2022 15:55:10 +0100 Subject: [PATCH 21/63] Add missing fields to ENF database --- .../exposurenotification/ExposureDatabase.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index 1dfb6114..a20b8bd6 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -71,13 +71,13 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TOKENS}_package_token ON $TABLE_TOKENS(package, token);") db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE(tcsid INTEGER PRIMARY KEY, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, matched INTEGER);") db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE}_key ON $TABLE_TEK_CHECK_SINGLE(keyData, rollingStartNumber, rollingPeriod);") - db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE_TOKEN(tcsid INTEGER REFERENCES $TABLE_TEK_CHECK_SINGLE(tcsid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcsid, tid));") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_SINGLE_TOKEN(tcsid INTEGER REFERENCES $TABLE_TEK_CHECK_SINGLE(tcsid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL, reportType INTEGER NOT NULL, daysSinceOnsetOfSymptoms INTEGER NOT NULL, UNIQUE(tcsid, tid));") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_SINGLE_TOKEN}_tid ON $TABLE_TEK_CHECK_SINGLE_TOKEN(tid);") db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE(tcfid INTEGER PRIMARY KEY, hash TEXT NOT NULL, endTimestamp INTEGER NOT NULL, keys INTEGER NOT NULL);") db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE}_hash ON $TABLE_TEK_CHECK_FILE(hash);") db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_TOKEN(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, tid INTEGER REFERENCES $TABLE_TOKENS(tid) ON DELETE CASCADE, UNIQUE(tcfid, tid));") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_TOKEN}_tid ON $TABLE_TEK_CHECK_FILE_TOKEN(tid);") - db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_MATCH(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, UNIQUE(tcfid, keyData, rollingStartNumber, rollingPeriod));") + db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_TEK_CHECK_FILE_MATCH(tcfid INTEGER REFERENCES $TABLE_TEK_CHECK_FILE(tcfid) ON DELETE CASCADE, keyData BLOB NOT NULL, rollingStartNumber INTEGER NOT NULL, rollingPeriod INTEGER NOT NULL, transmissionRiskLevel INTEGER NOT NULL, reportType INTEGER NOT NULL, daysSinceOnsetOfSymptoms INTEGER NOT NULL, UNIQUE(tcfid, keyData, rollingStartNumber, rollingPeriod));") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_tcfid ON $TABLE_TEK_CHECK_FILE_MATCH(tcfid);") db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_TEK_CHECK_FILE_MATCH}_key ON $TABLE_TEK_CHECK_FILE_MATCH(keyData, rollingStartNumber, rollingPeriod);") } @@ -89,6 +89,13 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit Log.d(TAG, "Altering tables for version >= 7") db.execSQL("ALTER TABLE $TABLE_TOKENS ADD COLUMN diagnosisKeysDataMap BLOB;") } + if (oldVersion in 5 until 10) { + Log.d(TAG, "Altering tables for version >= 10") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN reportType INTEGER NOT NULL;") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL;") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN reportType INTEGER NOT NULL;") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL;") + } if (oldVersion in 1 until 5) { Log.d(TAG, "Dropping legacy tables from version < 5") db.execSQL("DROP TABLE IF EXISTS $TABLE_CONFIGURATIONS;") @@ -284,6 +291,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit put("tid", tid) put("tcsid", tcsid) put("transmissionRiskLevel", key.transmissionRiskLevel) + put("reportType", key.reportType) + put("daysSinceOnsetOfSymptoms", key.daysSinceOnsetOfSymptoms) }) } @@ -368,12 +377,14 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit put("rollingStartNumber", key.rollingStartIntervalNumber) put("rollingPeriod", key.rollingPeriod) put("transmissionRiskLevel", key.transmissionRiskLevel) + put("reportType", key.reportType) + put("daysSinceOnsetOfSymptoms", key.daysSinceOnsetOfSymptoms) }) } private fun listMatchedSingleDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run { rawQuery(""" - SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod, $TABLE_TEK_CHECK_SINGLE_TOKEN.transmissionRiskLevel + SELECT $TABLE_TEK_CHECK_SINGLE.keyData, $TABLE_TEK_CHECK_SINGLE.rollingStartNumber, $TABLE_TEK_CHECK_SINGLE.rollingPeriod, $TABLE_TEK_CHECK_SINGLE_TOKEN.transmissionRiskLevel, $TABLE_TEK_CHECK_SINGLE_TOKEN.reportType, $TABLE_TEK_CHECK_SINGLE_TOKEN.daysSinceOnsetOfSymptoms FROM $TABLE_TEK_CHECK_SINGLE_TOKEN JOIN $TABLE_TEK_CHECK_SINGLE ON $TABLE_TEK_CHECK_SINGLE.tcsid = $TABLE_TEK_CHECK_SINGLE_TOKEN.tcsid WHERE @@ -387,6 +398,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit .setRollingStartIntervalNumber(cursor.getLong(1).toInt()) .setRollingPeriod(cursor.getLong(2).toInt()) .setTransmissionRiskLevel(cursor.getLong(3).toInt()) + .setReportType(cursor.getLong(4).toInt()) + .setDaysSinceOnsetOfSymptoms(cursor.getLong(5).toInt()) .build()) } list @@ -395,7 +408,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit private fun listMatchedFileDiagnosisKeys(tid: Long, database: SQLiteDatabase = readableDatabase) = database.run { rawQuery(""" - SELECT $TABLE_TEK_CHECK_FILE_MATCH.keyData, $TABLE_TEK_CHECK_FILE_MATCH.rollingStartNumber, $TABLE_TEK_CHECK_FILE_MATCH.rollingPeriod, $TABLE_TEK_CHECK_FILE_MATCH.transmissionRiskLevel + SELECT $TABLE_TEK_CHECK_FILE_MATCH.keyData, $TABLE_TEK_CHECK_FILE_MATCH.rollingStartNumber, $TABLE_TEK_CHECK_FILE_MATCH.rollingPeriod, $TABLE_TEK_CHECK_FILE_MATCH.transmissionRiskLevel, $TABLE_TEK_CHECK_FILE_MATCH.reportType, $TABLE_TEK_CHECK_FILE_MATCH.daysSinceOnsetOfSymptoms FROM $TABLE_TEK_CHECK_FILE_TOKEN JOIN $TABLE_TEK_CHECK_FILE_MATCH ON $TABLE_TEK_CHECK_FILE_MATCH.tcfid = $TABLE_TEK_CHECK_FILE_TOKEN.tcfid WHERE @@ -408,6 +421,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit .setRollingStartIntervalNumber(cursor.getLong(1).toInt()) .setRollingPeriod(cursor.getLong(2).toInt()) .setTransmissionRiskLevel(cursor.getLong(3).toInt()) + .setReportType(cursor.getLong(4).toInt()) + .setDaysSinceOnsetOfSymptoms(cursor.getLong(5).toInt()) .build()) } list @@ -823,7 +838,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit companion object { private const val DB_NAME = "exposure.db" - private const val DB_VERSION = 10 + private const val DB_VERSION = 11 private const val DB_SIZE_TOO_LARGE = 256L * 1024 * 1024 private const val MAX_DELETE_TIME = 5000L private const val TABLE_ADVERTISEMENTS = "advertisements" From ac779f9ac89e24159f07cde4fd4ad49b912a47ba Mon Sep 17 00:00:00 2001 From: Oliver S Date: Mon, 10 Jan 2022 16:05:01 +0100 Subject: [PATCH 22/63] Fix off by one error --- .../gms/nearby/exposurenotification/ExposureDatabase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index a20b8bd6..a4323a2a 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -89,8 +89,8 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit Log.d(TAG, "Altering tables for version >= 7") db.execSQL("ALTER TABLE $TABLE_TOKENS ADD COLUMN diagnosisKeysDataMap BLOB;") } - if (oldVersion in 5 until 10) { - Log.d(TAG, "Altering tables for version >= 10") + if (oldVersion in 5 until 11) { + Log.d(TAG, "Altering tables for version >= 11") db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN reportType INTEGER NOT NULL;") db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL;") db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN reportType INTEGER NOT NULL;") From a7eb9a2732732b756cd78fd9d831855013404c7b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 6 Dec 2021 18:26:23 +0100 Subject: [PATCH 23/63] Update firebase dynamic links API --- firebase-dynamic-links-api/build.gradle | 48 +++++-------- .../internal/DynamicLinkData.aidl | 9 ++- .../internal/IDynamicLinksCallbacks.aidl | 15 ++-- .../internal/IDynamicLinksService.aidl | 14 ++-- .../internal/ShortDynamicLink.aidl | 3 - .../internal/ShortDynamicLinkImpl.aidl | 10 +++ .../dynamiclinks/internal/Warning.aidl | 3 - .../dynamiclinks/internal/WarningImpl.aidl | 10 +++ .../dynamiclinks/ShortDynamicLink.java | 72 +++++++++++++++++++ .../internal/DynamicLinkData.java | 38 ++++------ .../internal/ShortDynamicLink.java | 50 ------------- .../internal/ShortDynamicLinkImpl.java | 38 ++++++++++ .../dynamiclinks/internal/Warning.java | 38 ---------- .../dynamiclinks/internal/WarningImpl.java | 40 +++++++++++ .../dynamiclinks/DynamicLinksServiceImpl.java | 11 ++- 15 files changed, 231 insertions(+), 168 deletions(-) delete mode 100644 firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.aidl create mode 100644 firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.aidl delete mode 100644 firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/Warning.aidl create mode 100644 firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/WarningImpl.aidl create mode 100644 firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/ShortDynamicLink.java delete mode 100644 firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.java create mode 100644 firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.java delete mode 100644 firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/Warning.java create mode 100644 firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/WarningImpl.java diff --git a/firebase-dynamic-links-api/build.gradle b/firebase-dynamic-links-api/build.gradle index 32edbc9b..7cb47747 100644 --- a/firebase-dynamic-links-api/build.gradle +++ b/firebase-dynamic-links-api/build.gradle @@ -1,49 +1,35 @@ /* - * Copyright 2019 e Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ apply plugin: 'com.android.library' - -String getMyVersionName() { - def stdout = new ByteArrayOutputStream() - if (rootProject.file("gradlew").exists()) - exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout } - else // automatic build system, don't tag dirty - exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout } - return stdout.toString().trim().substring(1) -} - -group = 'org.microg' -version = getMyVersionName() +apply plugin: 'maven-publish' +apply plugin: 'signing' android { - compileSdkVersion androidCompileSdk() + compileSdkVersion androidCompileSdk buildToolsVersion "$androidBuildVersionTools" defaultConfig { - versionName getMyVersionName() - minSdkVersion androidMinSdk() - targetSdkVersion androidTargetSdk() + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 } } dependencies { api project(':play-services-basement') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" } + +apply from: '../gradle/publish-android.gradle' + +description = 'microG API for firebase-dynamic-links' + diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/DynamicLinkData.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/DynamicLinkData.aidl index 5862178a..978e72cc 100644 --- a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/DynamicLinkData.aidl +++ b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/DynamicLinkData.aidl @@ -1,3 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + package com.google.firebase.dynamiclinks.internal; -parcelable DynamicLinkData; \ No newline at end of file +parcelable DynamicLinkData; diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksCallbacks.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksCallbacks.aidl index 9f9915ff..d13b9f28 100644 --- a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksCallbacks.aidl +++ b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksCallbacks.aidl @@ -1,12 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + package com.google.firebase.dynamiclinks.internal; - -import com.google.firebase.dynamiclinks.internal.DynamicLinkData; -import com.google.firebase.dynamiclinks.internal.ShortDynamicLink; - import com.google.android.gms.common.api.Status; +import com.google.firebase.dynamiclinks.internal.DynamicLinkData; +import com.google.firebase.dynamiclinks.internal.ShortDynamicLinkImpl; interface IDynamicLinksCallbacks { void onStatusDynamicLinkData(in Status status, in DynamicLinkData dldata) = 0; - void onStatusShortDynamicLink(in Status status, in ShortDynamicLink sdlink) = 1; + void onStatusShortDynamicLink(in Status status, in ShortDynamicLinkImpl sdlink) = 1; } diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksService.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksService.aidl index 57f083f5..3afdf705 100644 --- a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksService.aidl +++ b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/IDynamicLinksService.aidl @@ -1,12 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + package com.google.firebase.dynamiclinks.internal; - import com.google.firebase.dynamiclinks.internal.IDynamicLinksCallbacks; - import android.os.Bundle; - interface IDynamicLinksService { - void getInitialLink(IDynamicLinksCallbacks callback, String var2) = 0; - void func2(IDynamicLinksCallbacks callback, in Bundle var2) = 1; + void getInitialLink(IDynamicLinksCallbacks callback, String link) = 0; + void createShortDynamicLink(IDynamicLinksCallbacks callback, in Bundle extras) = 1; } diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.aidl deleted file mode 100644 index f4852a03..00000000 --- a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package com.google.firebase.dynamiclinks.internal; - -parcelable ShortDynamicLink; \ No newline at end of file diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.aidl new file mode 100644 index 00000000..cfca953f --- /dev/null +++ b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.aidl @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.firebase.dynamiclinks.internal; + +parcelable ShortDynamicLinkImpl; diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/Warning.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/Warning.aidl deleted file mode 100644 index 4331c92a..00000000 --- a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/Warning.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package com.google.firebase.dynamiclinks.internal; - -parcelable Warning; \ No newline at end of file diff --git a/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/WarningImpl.aidl b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/WarningImpl.aidl new file mode 100644 index 00000000..fbdb3f8f --- /dev/null +++ b/firebase-dynamic-links-api/src/main/aidl/com/google/firebase/dynamiclinks/internal/WarningImpl.aidl @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.firebase.dynamiclinks.internal; + +parcelable WarningImpl; diff --git a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/ShortDynamicLink.java b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/ShortDynamicLink.java new file mode 100644 index 00000000..f6133473 --- /dev/null +++ b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/ShortDynamicLink.java @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + * Notice: Portions of this file are reproduced from work created and shared by Google and used + * according to terms described in the Creative Commons 4.0 Attribution License. + * See https://developers.google.com/readme/policies for details. + */ + +package com.google.firebase.dynamiclinks; + +import android.net.Uri; + +import org.microg.gms.common.PublicApi; + +import java.util.List; + +/** + * Response from {@link DynamicLink.Builder#buildShortDynamicLink()} that returns the shortened Dynamic Link, link flow chart, and warnings from the requested Dynamic Link. + */ +@PublicApi +public interface ShortDynamicLink { + /** + * Gets the preview link to show the link flow chart. + */ + Uri getPreviewLink(); + + /** + * Gets the short Dynamic Link value. + */ + Uri getShortLink(); + + /** + * Gets information about potential warnings on link creation. + */ + List getWarnings(); + + /** + * Path generation option for short Dynamic Link length + */ + @interface Suffix { + /** + * Shorten the path to an unguessable string. Such strings are created by base62-encoding randomly generated + * 96-bit numbers, and consist of 17 alphanumeric characters. Use unguessable strings to prevent your Dynamic + * Links from being crawled, which can potentially expose sensitive information. + */ + int UNGUESSABLE = 1; + /** + * Shorten the path to a string that is only as long as needed to be unique, with a minimum length of 4 + * characters. Use this method if sensitive information would not be exposed if a short Dynamic Link URL were + * guessed. + */ + int SHORT = 2; + } + + /** + * Information about potential warnings on short Dynamic Link creation. + */ + interface Warning { + /** + * Gets the warning code. + * + * @deprecated See {@link #getMessage()} for more information on this warning and how to correct it. + */ + @Deprecated + String getCode(); + + /** + * Gets the warning message to help developers improve their requests. + */ + String getMessage(); + } +} diff --git a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java index 02270cd1..6e60b362 100644 --- a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java +++ b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/DynamicLinkData.java @@ -1,57 +1,45 @@ /* - * Copyright (C) 2019 e Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ package com.google.firebase.dynamiclinks.internal; - import org.microg.safeparcel.AutoSafeParcelable; import org.microg.safeparcel.SafeParceled; import android.os.Bundle; import android.net.Uri; - public class DynamicLinkData extends AutoSafeParcelable { - @SafeParceled(1) + @Field(1) public final String dynamicLink; - @SafeParceled(2) + @Field(2) public final String deepLink; - @SafeParceled(3) + @Field(3) public final int minVersion; - @SafeParceled(4) + @Field(4) public final long clickTimestamp; - @SafeParceled(5) + @Field(5) public final Bundle extensionBundle; - @SafeParceled(6) + @Field(6) public final Uri redirectUrl; public DynamicLinkData() { - dynamicLink = new String(); - deepLink = new String(); + dynamicLink = ""; + deepLink = ""; minVersion = 0; clickTimestamp = 0; extensionBundle = new Bundle(); redirectUrl = Uri.EMPTY; } - public static final Creator CREATOR = new AutoCreator(DynamicLinkData.class); -} \ No newline at end of file +} diff --git a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.java b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.java deleted file mode 100644 index 893e4098..00000000 --- a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLink.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2019 e Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package com.google.firebase.dynamiclinks.internal; - - -import com.google.firebase.dynamiclinks.internal.Warning; - -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; - -import android.net.Uri; -import java.util.List; -import java.util.ArrayList; - - -public class ShortDynamicLink extends AutoSafeParcelable { - @SafeParceled(1) - public final Uri shortLink; - - @SafeParceled(2) - public final Uri previewLink; - - @SafeParceled(3) - public final List warnings; - - - public ShortDynamicLink() { - shortLink = Uri.EMPTY; - previewLink = Uri.EMPTY; - - warnings = new ArrayList<>(); - } - - - public static final Creator CREATOR = new AutoCreator(ShortDynamicLink.class); -} \ No newline at end of file diff --git a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.java b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.java new file mode 100644 index 00000000..2c63b26e --- /dev/null +++ b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/ShortDynamicLinkImpl.java @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2019, e Foundation + * SPDX-FileCopyrightText: 2021, Google LLC + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.firebase.dynamiclinks.internal; + +import android.net.Uri; + +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.ArrayList; +import java.util.List; + + +public class ShortDynamicLinkImpl extends AutoSafeParcelable { + @Field(1) + public final Uri shortLink; + + @Field(2) + public final Uri previewLink; + + @Field(3) + public final List warnings; + + + public ShortDynamicLinkImpl() { + shortLink = Uri.EMPTY; + previewLink = Uri.EMPTY; + + warnings = new ArrayList<>(); + } + + + public static final Creator CREATOR = new AutoCreator(ShortDynamicLinkImpl.class); +} diff --git a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/Warning.java b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/Warning.java deleted file mode 100644 index 950f3de7..00000000 --- a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/Warning.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2019 e Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package com.google.firebase.dynamiclinks.internal; - - -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; - - -public class Warning extends AutoSafeParcelable { - @SafeParceled(1) - private int versionCode = 1; - - @SafeParceled(2) - public final String message; - - - public Warning() { - message = null; - } - - - public static final Creator CREATOR = new AutoCreator(Warning.class); -} \ No newline at end of file diff --git a/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/WarningImpl.java b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/WarningImpl.java new file mode 100644 index 00000000..ae8e9e15 --- /dev/null +++ b/firebase-dynamic-links-api/src/main/java/com/google/firebase/dynamiclinks/internal/WarningImpl.java @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.firebase.dynamiclinks.internal; + +import com.google.firebase.dynamiclinks.ShortDynamicLink; + +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; + +public class WarningImpl extends AutoSafeParcelable implements ShortDynamicLink.Warning { + @Field(1) + @Deprecated + private int code = 1; + + @Field(2) + private final String message; + + private WarningImpl() { + this.message = null; + } + + public WarningImpl(String message) { + this.message = message; + } + + @Override + public String getCode() { + return null; + } + + @Override + public String getMessage() { + return message; + } + + public static final Creator CREATOR = new AutoCreator(WarningImpl.class); +} diff --git a/play-services-core/src/main/java/org/microg/gms/firebase/dynamiclinks/DynamicLinksServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/firebase/dynamiclinks/DynamicLinksServiceImpl.java index a9f970b5..6602a87e 100644 --- a/play-services-core/src/main/java/org/microg/gms/firebase/dynamiclinks/DynamicLinksServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/firebase/dynamiclinks/DynamicLinksServiceImpl.java @@ -21,15 +21,13 @@ import android.os.RemoteException; import android.os.Bundle; import android.util.Log; import android.content.Context; -import android.content.Intent; import com.google.android.gms.common.api.Status; -import com.google.android.gms.common.api.CommonStatusCodes; import com.google.firebase.dynamiclinks.internal.IDynamicLinksService; import com.google.firebase.dynamiclinks.internal.IDynamicLinksCallbacks; import com.google.firebase.dynamiclinks.internal.DynamicLinkData; -import com.google.firebase.dynamiclinks.internal.ShortDynamicLink; +import com.google.firebase.dynamiclinks.internal.ShortDynamicLinkImpl; public class DynamicLinksServiceImpl extends IDynamicLinksService.Stub { @@ -40,15 +38,14 @@ public class DynamicLinksServiceImpl extends IDynamicLinksService.Stub { @Override - public void getInitialLink(IDynamicLinksCallbacks callback, String var2) throws RemoteException { + public void getInitialLink(IDynamicLinksCallbacks callback, String link) throws RemoteException { callback.onStatusDynamicLinkData(Status.SUCCESS, new DynamicLinkData()); } @Override - public void func2(IDynamicLinksCallbacks callback, Bundle var2) throws RemoteException { - Log.d(TAG, "func2: " + callback + ", " + var2); - callback.onStatusShortDynamicLink(Status.SUCCESS, new ShortDynamicLink()); + public void createShortDynamicLink(IDynamicLinksCallbacks callback, Bundle extras) throws RemoteException { + callback.onStatusShortDynamicLink(Status.SUCCESS, new ShortDynamicLinkImpl()); } From 41a32d93fe5f8434778f5adb4de2ddfc06d17567 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 6 Dec 2021 18:30:23 +0100 Subject: [PATCH 24/63] Move kotlin extension from play-services-basement to play-services-basement-ktx --- play-services-base-core/build.gradle | 2 +- play-services-basement-ktx/build.gradle | 32 +++++++++++++++++ .../src/main/AndroidManifest.xml | 7 ++++ .../android/gms/dynamic/ObjectWrapper.kt | 0 play-services-basement/build.gradle | 6 ---- settings.gradle | 36 ++++++++++--------- 6 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 play-services-basement-ktx/build.gradle create mode 100644 play-services-basement-ktx/src/main/AndroidManifest.xml rename {play-services-basement => play-services-basement-ktx}/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt (100%) diff --git a/play-services-base-core/build.gradle b/play-services-base-core/build.gradle index b9262817..809b3252 100644 --- a/play-services-base-core/build.gradle +++ b/play-services-base-core/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'maven-publish' apply plugin: 'signing' dependencies { - api project(':play-services-basement') + api project(':play-services-basement-ktx') api "androidx.lifecycle:lifecycle-service:$lifecycleVersion" implementation "androidx.annotation:annotation:$annotationVersion" diff --git a/play-services-basement-ktx/build.gradle b/play-services-basement-ktx/build.gradle new file mode 100644 index 00000000..67109194 --- /dev/null +++ b/play-services-basement-ktx/build.gradle @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(":play-services-basement") + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG kotlin extensions for play-services-basement' diff --git a/play-services-basement-ktx/src/main/AndroidManifest.xml b/play-services-basement-ktx/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1380d199 --- /dev/null +++ b/play-services-basement-ktx/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt b/play-services-basement-ktx/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt similarity index 100% rename from play-services-basement/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt rename to play-services-basement-ktx/src/main/kotlin/com/google/android/gms/dynamic/ObjectWrapper.kt diff --git a/play-services-basement/build.gradle b/play-services-basement/build.gradle index 84ebb440..30acbe04 100644 --- a/play-services-basement/build.gradle +++ b/play-services-basement/build.gradle @@ -15,7 +15,6 @@ */ apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' apply plugin: 'maven-publish' apply plugin: 'signing' @@ -23,7 +22,6 @@ dependencies { api "org.microg:safe-parcel:$safeParcelVersion" implementation "androidx.annotation:annotation:$annotationVersion" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" } android { @@ -42,10 +40,6 @@ android { buildConfigField "int", "VERSION_CODE", "$appVersionCode" } - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 diff --git a/settings.gradle b/settings.gradle index 23264525..dff2bb69 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,10 +17,30 @@ include ':play-services-wearable-api' include ':play-services-api' +include ':play-services-base' +include ':play-services-cast' +include ':play-services-droidguard' +include ':play-services-gcm' +include ':play-services-iid' +include ':play-services-location' +include ':play-services-nearby' +include ':play-services-vision' +include ':play-services-vision-common' +include ':play-services-wearable' + +include ':play-services' + +include ':firebase-auth-api' +include ':firebase-dynamic-links-api' + +// core only + include ':play-services-core-proto' include ':play-services-nearby-core-proto' include ':play-services-wearable-proto' +include ':play-services-basement-ktx' + include ':play-services-base-core' include ':play-services-conscrypt-provider-core' include ':play-services-cronet-core' @@ -35,23 +55,7 @@ include ':play-services-vision-core' include ':play-services-base-core-ui' include ':play-services-nearby-core-ui' -include ':firebase-auth-api' -include ':firebase-dynamic-links-api' - include ':firebase-auth-core' include ':play-services-core:microg-ui-tools' // Legacy include ':play-services-core' - -include ':play-services-base' -include ':play-services-cast' -include ':play-services-droidguard' -include ':play-services-gcm' -include ':play-services-iid' -include ':play-services-location' -include ':play-services-nearby' -include ':play-services-vision' -include ':play-services-vision-common' -include ':play-services-wearable' - -include ':play-services' From 6202aa9b34e181824a6861482d5bacc8c424cf5f Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 6 Dec 2021 18:31:21 +0100 Subject: [PATCH 25/63] Add play-services-tasks-ktx --- build.gradle | 2 +- play-services-tasks-ktx/build.gradle | 38 +++++ .../src/main/AndroidManifest.xml | 7 + .../com/google/android/gms/tasks/Tasks.kt | 152 ++++++++++++++++++ settings.gradle | 1 + 5 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 play-services-tasks-ktx/build.gradle create mode 100644 play-services-tasks-ktx/src/main/AndroidManifest.xml create mode 100644 play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt diff --git a/build.gradle b/build.gradle index 693d9fd5..f6fabfa8 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ext.wearableVersion = '0.1.1' ext.kotlinVersion = '1.4.32' - ext.coroutineVersion = '1.3.8' + ext.coroutineVersion = '1.5.2' ext.annotationVersion = '1.2.0' ext.appcompatVersion = '1.2.0' diff --git a/play-services-tasks-ktx/build.gradle b/play-services-tasks-ktx/build.gradle new file mode 100644 index 00000000..f39554a0 --- /dev/null +++ b/play-services-tasks-ktx/build.gradle @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(":play-services-tasks") + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = "1.8" + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG kotlin extensions for play-services-tasks' diff --git a/play-services-tasks-ktx/src/main/AndroidManifest.xml b/play-services-tasks-ktx/src/main/AndroidManifest.xml new file mode 100644 index 00000000..6a32b511 --- /dev/null +++ b/play-services-tasks-ktx/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt b/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt new file mode 100644 index 00000000..3171dd6e --- /dev/null +++ b/play-services-tasks-ktx/src/main/kotlin/com/google/android/gms/tasks/Tasks.kt @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2016, JetBrains s.r.o. + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.tasks + +import com.google.android.gms.tasks.* +import kotlinx.coroutines.* +import kotlin.coroutines.* + +/** + * Converts this deferred to the instance of [Task]. + * If deferred is cancelled then resulting task will be cancelled as well. + */ +fun Deferred.asTask(): Task { + val cancellation = CancellationTokenSource() + val source = TaskCompletionSource(cancellation.token) + + invokeOnCompletion callback@{ + if (it is CancellationException) { + cancellation.cancel() + return@callback + } + + val t = getCompletionExceptionOrNull() + if (t == null) { + source.setResult(getCompleted()) + } else { + source.setException(t as? Exception ?: RuntimeExecutionException(t)) + } + } + + return source.task +} + +/** + * Converts this task to an instance of [Deferred]. + * If task is cancelled then resulting deferred will be cancelled as well. + * However, the opposite is not true: if the deferred is cancelled, the [Task] will not be cancelled. + * For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used. + */ +fun Task.asDeferred(): Deferred = asDeferredImpl(null) + +/** + * Converts this task to an instance of [Deferred] with a [CancellationTokenSource] to control cancellation. + * The cancellation of this function is bi-directional: + * * If the given task is cancelled, the resulting deferred will be cancelled. + * * If the resulting deferred is cancelled, the provided [cancellationTokenSource] will be cancelled. + * + * Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and + * leads to an unspecified behaviour. + */ +@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0 +fun Task.asDeferred(cancellationTokenSource: CancellationTokenSource): Deferred = + asDeferredImpl(cancellationTokenSource) + +private fun Task.asDeferredImpl(cancellationTokenSource: CancellationTokenSource?): Deferred { + val deferred = CompletableDeferred() + if (isComplete) { + val e = exception + if (e == null) { + if (isCanceled) { + deferred.cancel() + } else { + @Suppress("UNCHECKED_CAST") + deferred.complete(result as T) + } + } else { + deferred.completeExceptionally(e) + } + } else { + addOnCompleteListener { + val e = it.exception + if (e == null) { + @Suppress("UNCHECKED_CAST") + if (it.isCanceled) deferred.cancel() else deferred.complete(it.result as T) + } else { + deferred.completeExceptionally(e) + } + } + } + + if (cancellationTokenSource != null) { + deferred.invokeOnCompletion { + cancellationTokenSource.cancel() + } + } + // Prevent casting to CompletableDeferred and manual completion. + return object : Deferred by deferred {} +} + +/** + * Awaits the completion of the task without blocking a thread. + * + * This suspending function is cancellable. + * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function + * stops waiting for the completion stage and immediately resumes with [CancellationException]. + * + * For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used. + */ +suspend fun Task.await(): T = awaitImpl(null) + +/** + * Awaits the completion of the task that is linked to the given [CancellationTokenSource] to control cancellation. + * + * This suspending function is cancellable and cancellation is bi-directional: + * * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function + * cancels the [cancellationTokenSource] and throws a [CancellationException]. + * * If the task is cancelled, then this function will throw a [CancellationException]. + * + * Providing a [CancellationTokenSource] that is unrelated to the receiving [Task] is not supported and + * leads to an unspecified behaviour. + */ +@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0 +suspend fun Task.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource) + +private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T { + // fast path + if (isComplete) { + val e = exception + return if (e == null) { + if (isCanceled) { + throw CancellationException("Task $this was cancelled normally.") + } else { + @Suppress("UNCHECKED_CAST") + result as T + } + } else { + throw e + } + } + + return suspendCancellableCoroutine { cont -> + addOnCompleteListener { + val e = it.exception + if (e == null) { + @Suppress("UNCHECKED_CAST") + if (it.isCanceled) cont.cancel() else cont.resume(it.result as T) + } else { + cont.resumeWithException(e) + } + } + + if (cancellationTokenSource != null) { + cont.invokeOnCancellation { + cancellationTokenSource.cancel() + } + } + } +} diff --git a/settings.gradle b/settings.gradle index dff2bb69..1d7ba5c6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -40,6 +40,7 @@ include ':play-services-nearby-core-proto' include ':play-services-wearable-proto' include ':play-services-basement-ktx' +include ':play-services-tasks-ktx' include ':play-services-base-core' include ':play-services-conscrypt-provider-core' From d8325870cb864db52dc5d91f525e57544e6f8b9d Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 8 Nov 2021 13:54:45 +0100 Subject: [PATCH 26/63] Add basic chimera classes for DroidGuard compatibility --- play-services-chimera-core/build.gradle | 41 ++++++ .../src/main/AndroidManifest.xml | 8 ++ .../android/chimera/InstanceProvider.java | 10 ++ .../google/android/chimera/IntentService.java | 118 +++++++++++++++ .../com/google/android/chimera/Service.java | 131 +++++++++++++++++ .../org/microg/gms/chimera/ServiceLoader.kt | 18 +++ .../org/microg/gms/chimera/ServiceProxy.kt | 134 ++++++++++++++++++ .../microg/gms/chimera/StaticServiceLoader.kt | 15 ++ settings.gradle | 1 + 9 files changed, 476 insertions(+) create mode 100644 play-services-chimera-core/build.gradle create mode 100644 play-services-chimera-core/src/main/AndroidManifest.xml create mode 100644 play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java create mode 100644 play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java create mode 100644 play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java create mode 100644 play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt create mode 100644 play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt create mode 100644 play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt diff --git a/play-services-chimera-core/build.gradle b/play-services-chimera-core/build.gradle new file mode 100644 index 00000000..d70a72c2 --- /dev/null +++ b/play-services-chimera-core/build.gradle @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-basement') + api "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + + implementation "androidx.annotation:annotation:$annotationVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG chimera implementation' diff --git a/play-services-chimera-core/src/main/AndroidManifest.xml b/play-services-chimera-core/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ffeb76d6 --- /dev/null +++ b/play-services-chimera-core/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java b/play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java new file mode 100644 index 00000000..d05b3448 --- /dev/null +++ b/play-services-chimera-core/src/main/java/com/google/android/chimera/InstanceProvider.java @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.chimera; + +public interface InstanceProvider { + Object getChimeraImpl(); +} diff --git a/play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java b/play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java new file mode 100644 index 00000000..51204deb --- /dev/null +++ b/play-services-chimera-core/src/main/java/com/google/android/chimera/IntentService.java @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.chimera; + +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; + +public abstract class IntentService extends Service { + private Looper serviceLooper; + private ServiceHandler serviceHandler; + private String name; + private boolean redelivery; + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + onHandleIntent((Intent)msg.obj); + stopSelf(msg.arg1); + } + } + + public IntentService(String name) { + this.name = name; + } + + /** + * Sets intent redelivery preferences. Usually called from the constructor + * with your preferred semantics. + * + *

If enabled is true, + * {@link #onStartCommand(Intent, int, int)} will return + * {@link Service#START_REDELIVER_INTENT}, so if this process dies before + * {@link #onHandleIntent(Intent)} returns, the process will be restarted + * and the intent redelivered. If multiple Intents have been sent, only + * the most recent one is guaranteed to be redelivered. + * + *

If enabled is false (the default), + * {@link #onStartCommand(Intent, int, int)} will return + * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent + * dies along with it. + */ + public void setIntentRedelivery(boolean redelivery) { + this.redelivery = redelivery; + } + + @Override + public void onCreate() { + super.onCreate(); + HandlerThread thread = new HandlerThread("IntentService[" + name + "]"); + thread.start(); + serviceLooper = thread.getLooper(); + serviceHandler = new ServiceHandler(serviceLooper); + } + + @Override + public void onStart(Intent intent, int startId) { + Message msg = this.serviceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = intent; + this.serviceHandler.sendMessage(msg); + } + + /** + * You should not override this method for your IntentService. Instead, + * override {@link #onHandleIntent}, which the system calls when the IntentService + * receives a start request. + * @see Service#onStartCommand + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + onStart(intent, startId); + return redelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; + } + + @Override + public void onDestroy() { + serviceLooper.quit(); + } + + /** + * Unless you provide binding for your service, you don't need to implement this + * method, because the default implementation returns null. + * @see Service#onBind + */ + @Override + public IBinder onBind(Intent intent) { + return null; + } + + /** + * This method is invoked on the worker thread with a request to process. + * Only one Intent is processed at a time, but the processing happens on a + * worker thread that runs independently from other application logic. + * So, if this code takes a long time, it will hold up other requests to + * the same IntentService, but it will not hold up anything else. + * When all requests have been handled, the IntentService stops itself, + * so you should not call {@link #stopSelf}. + * + * @param intent The value passed to {@link + * android.content.Context#startService(Intent)}. + * This may be null if the service is being restarted after + * its process has gone away; see + * {@link Service#onStartCommand} + * for details. + */ + public abstract void onHandleIntent(Intent intent); +} diff --git a/play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java b/play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java new file mode 100644 index 00000000..b8ed0e4e --- /dev/null +++ b/play-services-chimera-core/src/main/java/com/google/android/chimera/Service.java @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.chimera; + +import android.app.Application; +import android.app.Notification; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.IBinder; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +public abstract class Service extends ContextWrapper implements InstanceProvider { + public static final int START_CONTINUATION_MASK = 0xf; + public static final int START_FLAG_REDELIVERY = 1; + public static final int START_FLAG_RETRY = 2; + public static final int START_NOT_STICKY = 2; + public static final int START_REDELIVER_INTENT = 3; + public static final int START_STICKY = 1; + public static final int START_STICKY_COMPATIBILITY = 0; + + private android.app.Service containerService; + private ProxyCallbacks callbacks; + + public interface ProxyCallbacks { + void superOnCreate(); + + void superOnDestroy(); + + int superOnStartCommand(Intent intent, int flags, int startId); + + void superStopSelf(); + + void superStopSelf(int startId); + + boolean superStopSelfResult(int startId); + } + + public Service() { + super(null); + } + + protected void dump(FileDescriptor fs, PrintWriter writer, String[] args) { + } + + public final Application getApplication() { + return containerService.getApplication(); + } + + @Override + public Object getChimeraImpl() { + return this; + } + + public android.app.Service getContainerService() { + return containerService; + } + + public abstract IBinder onBind(Intent intent); + + + public void onConfigurationChanged(Configuration configuration) { + } + + public void onCreate() { + this.callbacks.superOnCreate(); + } + + public void onDestroy() { + this.callbacks.superOnDestroy(); + } + + public void onLowMemory() { + } + + public void onRebind(Intent intent) { + } + + public void onStart(Intent intent, int startId) { + } + + public int onStartCommand(Intent intent, int flags, int startId) { + return this.callbacks.superOnStartCommand(intent, flags, startId); + } + + public void onTaskRemoved(Intent rootIntent) { + } + + public void onTrimMemory(int level) { + } + + public boolean onUnbind(Intent intent) { + return false; + } + + public void publicDump(FileDescriptor fd, PrintWriter writer, String[] args) { + dump(fd, writer, args); + } + + public void setProxy(android.app.Service service, Context context) { + this.containerService = service; + this.callbacks = (ProxyCallbacks) service; + attachBaseContext(context); + } + + public final void startForeground(int id, Notification notification) { + this.containerService.startForeground(id, notification); + } + + public final void stopForeground(boolean removeNotification) { + this.containerService.stopForeground(removeNotification); + } + + public final void stopSelf() { + this.callbacks.superStopSelf(); + } + + public final boolean stopSelfResult(int startId) { + return this.callbacks.superStopSelfResult(startId); + } + + public final void stopSelf(int startId) { + this.callbacks.superStopSelf(startId); + } +} diff --git a/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt new file mode 100644 index 00000000..5e3da725 --- /dev/null +++ b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceLoader.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.chimera + +import android.content.Context +import com.google.android.chimera.Service + +interface ServiceLoader { + fun loadService(context: Context): Service + + companion object { + inline fun static() = StaticServiceLoader(T::class.java) + } +} + diff --git a/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt new file mode 100644 index 00000000..d61c242a --- /dev/null +++ b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/ServiceProxy.kt @@ -0,0 +1,134 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.chimera + +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import com.google.android.chimera.Service.ProxyCallbacks +import android.os.IBinder +import com.google.android.chimera.Service +import java.io.FileDescriptor +import java.io.PrintWriter + +abstract class ServiceProxy(private val loader: ServiceLoader) : android.app.Service(), ProxyCallbacks { + private var actualService: Service? = null + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + + if (actualService == null) { + val service = loader.loadService(base) + actualService = service + service.setProxy(this, this) + } + } + + override fun dump(fs: FileDescriptor, writer: PrintWriter, args: Array) { + if (actualService != null) { + actualService!!.publicDump(fs, writer, args) + } + } + + override fun onBind(intent: Intent): IBinder? { + return if (actualService != null) { + actualService!!.onBind(intent) + } else null + } + + override fun onConfigurationChanged(newConfig: Configuration) { + if (actualService != null) { + actualService!!.onConfigurationChanged(newConfig) + } + } + + override fun onCreate() { + if (actualService != null) { + actualService!!.onCreate() + } + } + + override fun onDestroy() { + if (actualService != null) { + actualService!!.onDestroy() + } + } + + override fun onLowMemory() { + if (actualService != null) { + actualService!!.onLowMemory() + } + } + + override fun onRebind(intent: Intent) { + if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onRebind(intent) + } + } + + override fun onStart(intent: Intent, startId: Int) { + if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onStart(intent, startId) + } else { + stopSelf(startId) + } + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onStartCommand(intent, flags, startId) + } else { + super.onStartCommand(intent, flags, startId) + } + } + + override fun onTaskRemoved(rootIntent: Intent) { + if (actualService != null) { + if (rootIntent != null) rootIntent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onTaskRemoved(rootIntent) + } + } + + override fun onTrimMemory(level: Int) { + if (actualService != null) { + actualService!!.onTrimMemory(level) + } + } + + override fun onUnbind(intent: Intent): Boolean { + return if (actualService != null) { + if (intent != null) intent.setExtrasClassLoader(actualService!!.classLoader) + actualService!!.onUnbind(intent) + } else { + false + } + } + + override fun superOnCreate() { + super.onCreate() + } + + override fun superOnDestroy() { + super.onDestroy() + } + + override fun superOnStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return super.onStartCommand(intent, flags, startId) + } + + override fun superStopSelf() { + super.stopSelf() + } + + override fun superStopSelf(startId: Int) { + super.stopSelf(startId) + } + + override fun superStopSelfResult(startId: Int): Boolean { + return super.stopSelfResult(startId) + } +} diff --git a/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt new file mode 100644 index 00000000..4989f177 --- /dev/null +++ b/play-services-chimera-core/src/main/kotlin/org/microg/gms/chimera/StaticServiceLoader.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.chimera + +import android.content.Context +import com.google.android.chimera.Service + +class StaticServiceLoader(private val serviceClass: Class) : ServiceLoader { + override fun loadService(context: Context): Service { + return serviceClass.getDeclaredConstructor().newInstance() + } +} diff --git a/settings.gradle b/settings.gradle index 1d7ba5c6..bda5a7e1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -43,6 +43,7 @@ include ':play-services-basement-ktx' include ':play-services-tasks-ktx' include ':play-services-base-core' +include ':play-services-chimera-core' include ':play-services-conscrypt-provider-core' include ':play-services-cronet-core' include ':play-services-location-core' From 6d45bfb7ed1a80db539ecd9bde492eeebd891b08 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 7 Oct 2021 10:20:42 +0200 Subject: [PATCH 27/63] Add DroidGuard support --- build.gradle | 2 +- .../java/org/microg/gms/common/Build.java | 44 +- .../org/microg/gms/common/PackageUtils.java | 12 + .../microg/gms/settings/SettingsContract.kt | 24 + .../microg/gms/settings/SettingsProvider.kt | 63 ++- .../microg/gms/utils/PackageManagerWrapper.kt | 529 ++++++++++++++++++ play-services-core/build.gradle | 2 + .../src/main/AndroidManifest.xml | 19 +- .../java/org/microg/gms/auth/AuthRequest.java | 2 +- .../org/microg/gms/checkin/CheckinClient.java | 2 +- .../gms/droidguard/DroidGuardService.java | 38 -- .../gms/droidguard/DroidGuardPreferences.kt | 70 --- .../gms/droidguard/DroidGuardResultCreator.kt | 70 --- .../org/microg/gms/droidguard/ServiceInfo.kt | 93 --- .../gms/safetynet/SafetyNetClientService.kt | 31 +- .../internal/DroidGuardInitReply.java | 10 +- .../build.gradle | 33 ++ .../src/main/proto/droidguard.proto | 73 +++ play-services-droidguard-core/build.gradle | 59 ++ .../src/main/AndroidManifest.xml | 28 + .../droidguard/DroidGuardChimeraService.java | 127 +++++ .../tracing/wrapper/TracingIntentService.java | 57 ++ .../microg/gms/droidguard/GuardCallback.java | 78 +++ .../microg/gms/droidguard/MediaDrmLock.java | 10 + .../microg/gms/droidguard/BytesException.kt | 22 + .../microg/gms/droidguard/DgDatabaseHelper.kt | 68 +++ .../gms/droidguard/DgpDatabaseHelper.kt | 19 + .../gms/droidguard/DroidGuardHandleImpl.kt | 112 ++++ .../gms/droidguard/DroidGuardPreferences.kt | 39 ++ .../gms/droidguard/DroidGuardResultCreator.kt | 72 +++ .../gms/droidguard/DroidGuardService.kt | 11 + .../gms/droidguard/DroidGuardServiceBroker.kt | 26 + .../gms/droidguard/DroidGuardServiceImpl.kt | 39 ++ .../microg/gms/droidguard/FallbackCreator.kt | 33 ++ .../org/microg/gms/droidguard/HandleProxy.kt | 53 ++ .../gms/droidguard/HandleProxyFactory.kt | 215 +++++++ .../gms/droidguard/SignatureVerifier.kt | 31 + .../org/microg/gms/droidguard/VersionUtil.kt | 75 +++ .../microg/gms/droidguard/DroidGuardClient.kt | 1 - .../gms/droidguard/DroidGuardClientImpl.kt | 3 +- settings.gradle | 2 + 41 files changed, 1970 insertions(+), 327 deletions(-) create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt delete mode 100644 play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt create mode 100644 play-services-droidguard-core-proto/build.gradle create mode 100644 play-services-droidguard-core-proto/src/main/proto/droidguard.proto create mode 100644 play-services-droidguard-core/build.gradle create mode 100644 play-services-droidguard-core/src/main/AndroidManifest.xml create mode 100644 play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java create mode 100644 play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java create mode 100644 play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java create mode 100644 play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt create mode 100644 play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt diff --git a/build.gradle b/build.gradle index f6fabfa8..92d7db8c 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ buildscript { ext.supportLibraryVersion = '28.0.0' ext.slf4jVersion = '1.7.25' - ext.volleyVersion = '1.2.0' + ext.volleyVersion = '1.2.1' ext.wireVersion = '3.2.2' ext.androidBuildGradleVersion = '4.1.0' diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/Build.java b/play-services-base-core/src/main/java/org/microg/gms/common/Build.java index 532a6845..c297c295 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/Build.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/Build.java @@ -16,32 +16,40 @@ package org.microg.gms.common; +import android.annotation.TargetApi; + import java.util.Locale; import java.util.Random; +// TODO: Make flexible public class Build { + public String board = android.os.Build.BOARD; + public String bootloader = android.os.Build.BOOTLOADER; + public String brand = android.os.Build.BRAND; + public String cpu_abi = android.os.Build.CPU_ABI; + public String cpu_abi2 = android.os.Build.CPU_ABI2; + @TargetApi(21) + public String[] supported_abis = android.os.Build.VERSION.SDK_INT >= 21 ? android.os.Build.SUPPORTED_ABIS : new String[0]; + public String device = android.os.Build.DEVICE; + public String display = android.os.Build.DISPLAY; public String fingerprint = android.os.Build.FINGERPRINT; public String hardware = android.os.Build.HARDWARE; - public String brand = android.os.Build.BRAND; - public String radio = getRadio(); - public String bootloader = android.os.Build.BOOTLOADER; - public long time = android.os.Build.TIME; - public String device = android.os.Build.DEVICE; - public int sdk = android.os.Build.VERSION.SDK_INT; - public String model = android.os.Build.MODEL; - public String manufacturer = android.os.Build.MANUFACTURER; - public String product = android.os.Build.PRODUCT; + public String host = android.os.Build.HOST; public String id = android.os.Build.ID; + public String manufacturer = android.os.Build.MANUFACTURER; + public String model = android.os.Build.MODEL; + public String product = android.os.Build.PRODUCT; + public String radio = android.os.Build.RADIO; public String serial = generateSerialNumber(); // TODO: static - - @SuppressWarnings("deprecation") - private static String getRadio() { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - return android.os.Build.getRadioVersion(); - } else { - return android.os.Build.RADIO; - } - } + public String tags = android.os.Build.TAGS; + public long time = android.os.Build.TIME; + public String type = android.os.Build.TYPE; + public String user = android.os.Build.USER; + public String version_codename = android.os.Build.VERSION.CODENAME; + public String version_incremental = android.os.Build.VERSION.INCREMENTAL; + public String version_release = android.os.Build.VERSION.RELEASE; + public String version_sdk = android.os.Build.VERSION.SDK; + public int version_sdk_int = android.os.Build.VERSION.SDK_INT; private String generateSerialNumber() { String serial = "008741"; diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 4abbcd2a..08a0db17 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -152,6 +152,18 @@ public class PackageUtils { return getAndCheckCallingPackage(context, suggestedPackageName, 0); } + @Nullable + public static String getAndCheckCallingPackageOrExtendedAccess(Context context, String suggestedPackageName) { + try { + return getAndCheckCallingPackage(context, suggestedPackageName, 0); + } catch (Exception e) { + if (callerHasExtendedAccess(context)) { + return suggestedPackageName; + } + throw e; + } + } + @Nullable public static String getAndCheckCallingPackage(Context context, int suggestedCallerUid) { return getAndCheckCallingPackage(context, null, suggestedCallerUid); 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 1287c3ea..7132c7cf 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 @@ -107,6 +107,28 @@ object SettingsContract { private const val id = "safety-net" fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + + const val ENABLED = "safetynet_enabled" + + val PROJECTION = arrayOf( + ENABLED + ) + } + + object DroidGuard { + private const val id = "droidguard" + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + + const val ENABLED = "droidguard_enabled" + const val MODE = "droidguard_mode" + const val NETWORK_SERVER_URL = "droidguard_network_server_url" + + val PROJECTION = arrayOf( + ENABLED, + MODE, + NETWORK_SERVER_URL + ) } private fun withoutCallingIdentity(f: () -> T): T { @@ -118,6 +140,7 @@ object SettingsContract { } } + @JvmStatic fun getSettings(context: Context, uri: Uri, projection: Array?, f: (Cursor) -> T): T = withoutCallingIdentity { context.contentResolver.query(uri, projection, null, null, null).use { c -> require(c != null) { "Cursor for query $uri ${projection?.toList()} was null" } @@ -126,6 +149,7 @@ object SettingsContract { } } + @JvmStatic fun setSettings(context: Context, uri: Uri, v: ContentValues.() -> Unit) = withoutCallingIdentity { val values = ContentValues().apply { v.invoke(this) } val affected = context.contentResolver.update(uri, values, null, null) 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 b59da582..3d1f712f 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 @@ -17,8 +17,10 @@ import android.preference.PreferenceManager import org.microg.gms.common.PackageUtils.warnIfNotMainProcess import org.microg.gms.settings.SettingsContract.Auth import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.DroidGuard import org.microg.gms.settings.SettingsContract.Exposure import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.SafetyNet import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File @@ -61,6 +63,8 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> queryGcm(projection ?: Gcm.PROJECTION) Auth.getContentUri(context!!) -> queryAuth(projection ?: Auth.PROJECTION) Exposure.getContentUri(context!!) -> queryExposure(projection ?: Exposure.PROJECTION) + SafetyNet.getContentUri(context!!) -> querySafetyNet(projection ?: SafetyNet.PROJECTION) + DroidGuard.getContentUri(context!!) -> queryDroidGuard(projection ?: DroidGuard.PROJECTION) else -> null } @@ -77,6 +81,8 @@ class SettingsProvider : ContentProvider() { Gcm.getContentUri(context!!) -> updateGcm(values) Auth.getContentUri(context!!) -> updateAuth(values) Exposure.getContentUri(context!!) -> updateExposure(values) + SafetyNet.getContentUri(context!!) -> updateSafetyNet(values) + DroidGuard.getContentUri(context!!) -> updateDroidGuard(values) else -> return 0 } return 1 @@ -216,9 +222,51 @@ class SettingsProvider : ContentProvider() { editor.apply() } + private fun querySafetyNet(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + SafetyNet.ENABLED -> getSettingsBoolean(key, false) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + + private fun updateSafetyNet(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + SafetyNet.ENABLED -> editor.putBoolean(key, value as Boolean) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + + private fun queryDroidGuard(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + DroidGuard.ENABLED -> getSettingsBoolean(key, false) + DroidGuard.MODE -> getSettingsString(key) + DroidGuard.NETWORK_SERVER_URL -> getSettingsString(key) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + + private fun updateDroidGuard(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + DroidGuard.ENABLED -> editor.putBoolean(key, value as Boolean) + DroidGuard.MODE -> editor.putString(key, value as String) + DroidGuard.NETWORK_SERVER_URL -> editor.putString(key, value as String) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + private fun MatrixCursor.addRow( p: Array, - valueGetter: (String) -> Any + valueGetter: (String) -> Any? ): MatrixCursor { val row = newRow() for (key in p) row.add(valueGetter.invoke(key)) @@ -243,7 +291,16 @@ class SettingsProvider : ContentProvider() { * @return the current setting as [Int], because [ContentProvider] does not support [Boolean]. */ private fun getSettingsBoolean(key: String, def: Boolean): Int { - val default = systemDefaultPreferences?.getBoolean(key, def) ?: def - return if (preferences.getBoolean(key, default)) 1 else 0 + return listOf(preferences, systemDefaultPreferences).getBooleanAsInt(key, def) } + + private fun getSettingsString(key: String, def: String? = null): String? = listOf(preferences, systemDefaultPreferences).getString(key, def) + private fun getSettingsInt(key: String, def: Int): Int = listOf(preferences, systemDefaultPreferences).getInt(key, def) + private fun getSettingsLong(key: String, def: Long): Long = listOf(preferences, systemDefaultPreferences).getLong(key, def) + + private fun List.getString(key: String, def: String?): String? = foldRight(def) { preferences, defValue -> preferences?.getString(key, defValue) ?: defValue } + private fun List.getInt(key: String, def: Int): Int = foldRight(def) { preferences, defValue -> preferences?.getInt(key, defValue) ?: defValue } + private fun List.getLong(key: String, def: Long): Long = foldRight(def) { preferences, defValue -> preferences?.getLong(key, defValue) ?: defValue } + private fun List.getBoolean(key: String, def: Boolean): Boolean = foldRight(def) { preferences, defValue -> preferences?.getBoolean(key, defValue) ?: defValue } + private fun List.getBooleanAsInt(key: String, def: Boolean): Int = if (getBoolean(key, def)) 1 else 0 } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt new file mode 100644 index 00000000..4a958a57 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt @@ -0,0 +1,529 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.utils + +import android.annotation.TargetApi +import android.content.ComponentName +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.* +import android.content.res.Resources +import android.content.res.XmlResourceParser +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.os.UserHandle +import androidx.annotation.RequiresApi + +open class PackageManagerWrapper(private val wrapped: PackageManager) : PackageManager() { + override fun getPackageInfo(packageName: String, flags: Int): PackageInfo { + return wrapped.getPackageInfo(packageName, flags) + } + + @TargetApi(26) + override fun getPackageInfo(versionedPackage: VersionedPackage, flags: Int): PackageInfo { + return wrapped.getPackageInfo(versionedPackage, flags) + } + + override fun currentToCanonicalPackageNames(packageNames: Array): Array { + return wrapped.currentToCanonicalPackageNames(packageNames) + } + + override fun canonicalToCurrentPackageNames(packageNames: Array): Array { + return wrapped.canonicalToCurrentPackageNames(packageNames) + } + + override fun getLaunchIntentForPackage(packageName: String): Intent? { + return wrapped.getLaunchIntentForPackage(packageName) + } + + @TargetApi(21) + override fun getLeanbackLaunchIntentForPackage(packageName: String): Intent? { + return wrapped.getLeanbackLaunchIntentForPackage(packageName) + } + + override fun getPackageGids(packageName: String): IntArray { + return wrapped.getPackageGids(packageName) + } + + @TargetApi(24) + override fun getPackageGids(packageName: String, flags: Int): IntArray { + return wrapped.getPackageGids(packageName, flags) + } + + @TargetApi(24) + override fun getPackageUid(packageName: String, flags: Int): Int { + return wrapped.getPackageUid(packageName, flags) + } + + override fun getPermissionInfo(permName: String, flags: Int): PermissionInfo { + return wrapped.getPermissionInfo(permName, flags) + } + + override fun queryPermissionsByGroup(permissionGroup: String, flags: Int): MutableList { + return wrapped.queryPermissionsByGroup(permissionGroup, flags) + } + + override fun getPermissionGroupInfo(permName: String, flags: Int): PermissionGroupInfo { + return wrapped.getPermissionGroupInfo(permName, flags) + } + + override fun getAllPermissionGroups(flags: Int): MutableList { + return wrapped.getAllPermissionGroups(flags) + } + + override fun getApplicationInfo(packageName: String, flags: Int): ApplicationInfo { + return wrapped.getApplicationInfo(packageName, flags) + } + + override fun getActivityInfo(component: ComponentName, flags: Int): ActivityInfo { + return wrapped.getActivityInfo(component, flags) + } + + override fun getReceiverInfo(component: ComponentName, flags: Int): ActivityInfo { + return wrapped.getReceiverInfo(component, flags) + } + + override fun getServiceInfo(component: ComponentName, flags: Int): ServiceInfo { + return wrapped.getServiceInfo(component, flags) + } + + override fun getProviderInfo(component: ComponentName, flags: Int): ProviderInfo { + return wrapped.getProviderInfo(component, flags) + } + + @RequiresApi(29) + override fun getInstalledModules(flags: Int): MutableList { + return wrapped.getInstalledModules(flags) + } + + override fun getInstalledPackages(flags: Int): MutableList { + return wrapped.getInstalledPackages(flags) + } + + @TargetApi(18) + override fun getPackagesHoldingPermissions(permissions: Array, flags: Int): MutableList { + return wrapped.getPackagesHoldingPermissions(permissions, flags) + } + + override fun checkPermission(permName: String, packageName: String): Int { + return wrapped.checkPermission(permName, packageName) + } + + @TargetApi(23) + override fun isPermissionRevokedByPolicy(permName: String, packageName: String): Boolean { + return wrapped.isPermissionRevokedByPolicy(permName, packageName) + } + + override fun addPermission(info: PermissionInfo): Boolean { + return wrapped.addPermission(info) + } + + override fun addPermissionAsync(info: PermissionInfo): Boolean { + return wrapped.addPermissionAsync(info) + } + + override fun removePermission(permName: String) { + return wrapped.removePermission(permName) + } + + override fun checkSignatures(packageName1: String, packageName2: String): Int { + return wrapped.checkSignatures(packageName1, packageName2) + } + + override fun checkSignatures(uid1: Int, uid2: Int): Int { + return wrapped.checkSignatures(uid1, uid2) + } + + override fun getPackagesForUid(uid: Int): Array? { + return wrapped.getPackagesForUid(uid) + } + + override fun getNameForUid(uid: Int): String? { + return wrapped.getNameForUid(uid) + } + + override fun getInstalledApplications(flags: Int): MutableList { + return wrapped.getInstalledApplications(flags) + } + + @TargetApi(26) + override fun isInstantApp(): Boolean { + return wrapped.isInstantApp + } + + @TargetApi(26) + override fun isInstantApp(packageName: String): Boolean { + return wrapped.isInstantApp(packageName) + } + + @TargetApi(26) + override fun getInstantAppCookieMaxBytes(): Int { + return wrapped.instantAppCookieMaxBytes + } + + @TargetApi(26) + override fun getInstantAppCookie(): ByteArray { + return wrapped.instantAppCookie + } + + @TargetApi(26) + override fun clearInstantAppCookie() { + return wrapped.clearInstantAppCookie() + } + + @TargetApi(26) + override fun updateInstantAppCookie(cookie: ByteArray?) { + return wrapped.updateInstantAppCookie(cookie) + } + + @TargetApi(26) + override fun getSystemSharedLibraryNames(): Array? { + return wrapped.systemSharedLibraryNames + } + + @TargetApi(26) + override fun getSharedLibraries(flags: Int): MutableList { + return wrapped.getSharedLibraries(flags) + } + + @TargetApi(26) + override fun getChangedPackages(sequenceNumber: Int): ChangedPackages? { + return wrapped.getChangedPackages(sequenceNumber) + } + + override fun getSystemAvailableFeatures(): Array { + return wrapped.systemAvailableFeatures + } + + override fun hasSystemFeature(featureName: String): Boolean { + return wrapped.hasSystemFeature(featureName) + } + + @TargetApi(24) + override fun hasSystemFeature(featureName: String, version: Int): Boolean { + return wrapped.hasSystemFeature(featureName, version) + } + + override fun resolveActivity(intent: Intent, flags: Int): ResolveInfo? { + return wrapped.resolveActivity(intent, flags) + } + + override fun queryIntentActivities(intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentActivities(intent, flags) + } + + override fun queryIntentActivityOptions(caller: ComponentName?, specifics: Array?, intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentActivityOptions(caller, specifics, intent, flags) + } + + override fun queryBroadcastReceivers(intent: Intent, flags: Int): MutableList { + return wrapped.queryBroadcastReceivers(intent, flags) + } + + override fun resolveService(intent: Intent, flags: Int): ResolveInfo? { + return wrapped.resolveService(intent, flags) + } + + override fun queryIntentServices(intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentServices(intent, flags) + } + + @TargetApi(19) + override fun queryIntentContentProviders(intent: Intent, flags: Int): MutableList { + return wrapped.queryIntentContentProviders(intent, flags) + } + + override fun resolveContentProvider(authority: String, flags: Int): ProviderInfo? { + return wrapped.resolveContentProvider(authority, flags) + } + + override fun queryContentProviders(processName: String?, uid: Int, flags: Int): MutableList { + return wrapped.queryContentProviders(processName, uid, flags) + } + + override fun getInstrumentationInfo(className: ComponentName, flags: Int): InstrumentationInfo { + return wrapped.getInstrumentationInfo(className, flags) + } + + override fun queryInstrumentation(targetPackage: String, flags: Int): MutableList { + return wrapped.queryInstrumentation(targetPackage, flags) + } + + override fun getDrawable(packageName: String, resid: Int, appInfo: ApplicationInfo?): Drawable? { + return wrapped.getDrawable(packageName, resid, appInfo) + } + + override fun getActivityIcon(activityName: ComponentName): Drawable { + return wrapped.getActivityIcon(activityName) + } + + override fun getActivityIcon(intent: Intent): Drawable { + return wrapped.getActivityIcon(intent) + } + + @TargetApi(20) + override fun getActivityBanner(activityName: ComponentName): Drawable? { + return wrapped.getActivityBanner(activityName) + } + + @TargetApi(20) + override fun getActivityBanner(intent: Intent): Drawable? { + return wrapped.getActivityBanner(intent) + } + + override fun getDefaultActivityIcon(): Drawable { + return wrapped.defaultActivityIcon + } + + override fun getApplicationIcon(info: ApplicationInfo): Drawable { + return wrapped.getApplicationIcon(info) + } + + override fun getApplicationIcon(packageName: String): Drawable { + return wrapped.getApplicationIcon(packageName) + } + + @TargetApi(20) + override fun getApplicationBanner(info: ApplicationInfo): Drawable? { + return wrapped.getApplicationBanner(info) + } + + @TargetApi(20) + override fun getApplicationBanner(packageName: String): Drawable? { + return wrapped.getApplicationBanner(packageName) + } + + override fun getActivityLogo(activityName: ComponentName): Drawable? { + return wrapped.getActivityLogo(activityName) + } + + override fun getActivityLogo(intent: Intent): Drawable? { + return wrapped.getActivityLogo(intent) + } + + override fun getApplicationLogo(info: ApplicationInfo): Drawable? { + return wrapped.getApplicationLogo(info) + } + + override fun getApplicationLogo(packageName: String): Drawable? { + return wrapped.getApplicationLogo(packageName) + } + + @TargetApi(21) + override fun getUserBadgedIcon(drawable: Drawable, user: UserHandle): Drawable { + return wrapped.getUserBadgedIcon(drawable, user) + } + + @TargetApi(21) + override fun getUserBadgedDrawableForDensity(drawable: Drawable, user: UserHandle, badgeLocation: Rect?, badgeDensity: Int): Drawable { + return wrapped.getUserBadgedDrawableForDensity(drawable, user, badgeLocation, badgeDensity) + } + + @TargetApi(21) + override fun getUserBadgedLabel(label: CharSequence, user: UserHandle): CharSequence { + return wrapped.getUserBadgedLabel(label, user) + } + + override fun getText(packageName: String, resid: Int, appInfo: ApplicationInfo?): CharSequence? { + return wrapped.getText(packageName, resid, appInfo) + } + + override fun getXml(packageName: String, resid: Int, appInfo: ApplicationInfo?): XmlResourceParser? { + return wrapped.getXml(packageName, resid, appInfo) + } + + override fun getApplicationLabel(info: ApplicationInfo): CharSequence { + return wrapped.getApplicationLabel(info) + } + + override fun getResourcesForActivity(activityName: ComponentName): Resources { + return wrapped.getResourcesForActivity(activityName) + } + + override fun getResourcesForApplication(app: ApplicationInfo): Resources { + return wrapped.getResourcesForApplication(app) + } + + override fun getResourcesForApplication(packageName: String): Resources { + return wrapped.getResourcesForApplication(packageName) + } + + override fun verifyPendingInstall(id: Int, verificationCode: Int) { + return wrapped.verifyPendingInstall(id, verificationCode) + } + + @TargetApi(17) + override fun extendVerificationTimeout(id: Int, verificationCodeAtTimeout: Int, millisecondsToDelay: Long) { + return wrapped.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay) + } + + override fun setInstallerPackageName(targetPackage: String, installerPackageName: String?) { + return wrapped.setInstallerPackageName(targetPackage, installerPackageName) + } + + override fun getInstallerPackageName(packageName: String): String? { + return wrapped.getInstallerPackageName(packageName) + } + + override fun addPackageToPreferred(packageName: String) { + return wrapped.addPackageToPreferred(packageName) + } + + override fun removePackageFromPreferred(packageName: String) { + return wrapped.removePackageFromPreferred(packageName) + } + + override fun getPreferredPackages(flags: Int): MutableList { + return wrapped.getPreferredPackages(flags) + } + + override fun addPreferredActivity(filter: IntentFilter, match: Int, set: Array?, activity: ComponentName) { + return wrapped.addPreferredActivity(filter, match, set, activity) + } + + override fun clearPackagePreferredActivities(packageName: String) { + return wrapped.clearPackagePreferredActivities(packageName) + } + + override fun getPreferredActivities(outFilters: MutableList, outActivities: MutableList, packageName: String?): Int { + return wrapped.getPreferredActivities(outFilters, outActivities, packageName) + } + + override fun setComponentEnabledSetting(componentName: ComponentName, newState: Int, flags: Int) { + return wrapped.setComponentEnabledSetting(componentName, newState, flags) + } + + override fun getComponentEnabledSetting(componentName: ComponentName): Int { + return wrapped.getComponentEnabledSetting(componentName) + } + + override fun setApplicationEnabledSetting(packageName: String, newState: Int, flags: Int) { + return wrapped.setApplicationEnabledSetting(packageName, newState, flags) + } + + override fun getApplicationEnabledSetting(packageName: String): Int { + return wrapped.getApplicationEnabledSetting(packageName) + } + + override fun isSafeMode(): Boolean { + return wrapped.isSafeMode + } + + @TargetApi(26) + override fun setApplicationCategoryHint(packageName: String, categoryHint: Int) { + return wrapped.setApplicationCategoryHint(packageName, categoryHint) + } + + @TargetApi(21) + override fun getPackageInstaller(): PackageInstaller { + return wrapped.packageInstaller + } + + @TargetApi(26) + override fun canRequestPackageInstalls(): Boolean { + return wrapped.canRequestPackageInstalls() + } + + + @TargetApi(29) + override fun addWhitelistedRestrictedPermission(packageName: String, permName: String, whitelistFlags: Int): Boolean { + return wrapped.addWhitelistedRestrictedPermission(packageName, permName, whitelistFlags) + } + + @TargetApi(30) + override fun getBackgroundPermissionOptionLabel(): CharSequence { + return wrapped.getBackgroundPermissionOptionLabel() + } + + @TargetApi(30) + override fun getInstallSourceInfo(packageName: String): InstallSourceInfo { + return wrapped.getInstallSourceInfo(packageName) + } + + @TargetApi(30) + override fun getMimeGroup(mimeGroup: String): MutableSet { + return wrapped.getMimeGroup(mimeGroup) + } + + @TargetApi(29) + override fun getModuleInfo(packageName: String, flags: Int): ModuleInfo { + return wrapped.getModuleInfo(packageName, flags) + } + + override fun getPackageArchiveInfo(archiveFilePath: String, flags: Int): PackageInfo? { + return wrapped.getPackageArchiveInfo(archiveFilePath, flags) + } + + @TargetApi(28) + override fun getSuspendedPackageAppExtras(): Bundle? { + return wrapped.suspendedPackageAppExtras + } + + @TargetApi(29) + override fun getSyntheticAppDetailsActivityEnabled(packageName: String): Boolean { + return wrapped.getSyntheticAppDetailsActivityEnabled(packageName) + } + + @TargetApi(29) + override fun getWhitelistedRestrictedPermissions(packageName: String, whitelistFlag: Int): MutableSet { + return wrapped.getWhitelistedRestrictedPermissions(packageName, whitelistFlag) + } + + @TargetApi(28) + override fun hasSigningCertificate(packageName: String, certificate: ByteArray, type: Int): Boolean { + return wrapped.hasSigningCertificate(packageName, certificate, type) + } + + @TargetApi(28) + override fun hasSigningCertificate(uid: Int, certificate: ByteArray, type: Int): Boolean { + return wrapped.hasSigningCertificate(uid, certificate, type) + } + + @TargetApi(30) + override fun isAutoRevokeWhitelisted(): Boolean { + return wrapped.isAutoRevokeWhitelisted + } + + @TargetApi(30) + override fun isAutoRevokeWhitelisted(packageName: String): Boolean { + return wrapped.isAutoRevokeWhitelisted(packageName) + } + + @TargetApi(30) + override fun isDefaultApplicationIcon(drawable: Drawable): Boolean { + return wrapped.isDefaultApplicationIcon(drawable) + } + + @TargetApi(29) + override fun isDeviceUpgrading(): Boolean { + return wrapped.isDeviceUpgrading + } + + @TargetApi(28) + override fun isPackageSuspended(): Boolean { + return wrapped.isPackageSuspended + } + + @TargetApi(29) + override fun isPackageSuspended(packageName: String): Boolean { + return wrapped.isPackageSuspended(packageName) + } + + @TargetApi(29) + override fun removeWhitelistedRestrictedPermission(packageName: String, permName: String, whitelistFlags: Int): Boolean { + return wrapped.removeWhitelistedRestrictedPermission(packageName, permName, whitelistFlags) + } + + @TargetApi(30) + override fun setAutoRevokeWhitelisted(packageName: String, whitelisted: Boolean): Boolean { + return wrapped.setAutoRevokeWhitelisted(packageName, whitelisted) + } + + @TargetApi(30) + override fun setMimeGroup(mimeGroup: String, mimeTypes: MutableSet) { + return wrapped.setMimeGroup(mimeGroup, mimeTypes) + } +} diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 9f87ea76..1209c603 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -44,6 +44,8 @@ dependencies { implementation project(':play-services-base-core-ui') implementation project(':play-services-conscrypt-provider-core') implementation project(':play-services-cronet-core') + implementation project(':play-services-droidguard') // TODO: Move to play-services-safetynet-core once we have it + implementation project(':play-services-droidguard-core') implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 693ec889..e4fa697d 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -26,10 +26,12 @@ + android:permissionGroup="android.permission-group.NETWORK" + android:protectionLevel="privileged|signature" /> + + + @@ -294,16 +299,6 @@ - - - - - - - - - - diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java index 8dda8e4c..b65648cb 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java @@ -92,7 +92,7 @@ public class AuthRequest extends HttpFormClient.Request { } public AuthRequest build(Build build) { - sdkVersion = build.sdk; + sdkVersion = build.version_sdk_int; deviceName = build.device; buildVersion = build.id; return this; diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 0b56919f..9186d1a3 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -97,7 +97,7 @@ public class CheckinClient { //.packageVersionCode(Constants.MAX_REFERENCE_VERSION) .product(build.product) .radio(build.radio) - .sdkVersion(build.sdk) + .sdkVersion(build.version_sdk_int) .time(build.time / 1000) .build()) .cellOperator(phoneInfo.cellOperator) diff --git a/play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java b/play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java deleted file mode 100644 index 7760862d..00000000 --- a/play-services-core/src/main/java/org/microg/gms/droidguard/DroidGuardService.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package org.microg.gms.droidguard; - -import android.util.Log; - -import com.google.android.gms.common.internal.GetServiceRequest; -import com.google.android.gms.common.internal.IGmsCallbacks; - -import org.microg.gms.BaseService; -import org.microg.gms.common.GmsService; - -public class DroidGuardService extends BaseService { - - public DroidGuardService() { - super("GmsDroidGuardSvc", GmsService.DROIDGUARD); - } - - @Override - public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) { - // TODO - Log.d(TAG, "handleServiceRequest"); - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt deleted file mode 100644 index 1d9fd028..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.droidguard - -import android.content.Context -import android.content.SharedPreferences -import java.io.File - -class DroidGuardPreferences(private val context: Context) { - @Suppress("DEPRECATION") - private val preferences by lazy { context.getSharedPreferences("droidguard", Context.MODE_PRIVATE) } - private val systemDefaultPreferences by lazy { - try { - Context::class.java.getDeclaredMethod("getSharedPreferences", File::class.java, Int::class.javaPrimitiveType).invoke(context, File("/system/etc/microg.xml"), Context.MODE_PRIVATE) as SharedPreferences - } catch (ignored: Exception) { - null - } - } - private var editing: Boolean = false - private val updates: MutableMap = hashMapOf() - - var mode: Mode - get() = try { - getSettingsString(PREF_DROIDGUARD_MODE)?.let { Mode.valueOf(it) } ?: Mode.Connector - } catch (e: Exception) { - Mode.Connector - } - set(value) { - if (editing) updates[PREF_DROIDGUARD_MODE] = value.name - } - - var networkServerUrl: String? - get() = getSettingsString(PREF_DROIDGUARD_NETWORK_SERVER_URL) - set(value) { - if (editing) updates[PREF_DROIDGUARD_NETWORK_SERVER_URL] = value - } - - private fun getSettingsString(key: String): String? { - return systemDefaultPreferences?.getString(key, null) ?: preferences.getString(key, null) - } - - fun edit(commands: DroidGuardPreferences.() -> Unit) { - editing = true - commands(this) - preferences.edit().also { - for ((k, v) in updates) { - when (v) { - is String -> it.putString(k, v) - null -> it.remove(k) - } - } - }.apply() - editing = false - } - - enum class Mode { - Disabled, - Connector, - Network - } - - companion object { - const val PREF_DROIDGUARD_MODE = "droidguard_mode" - const val PREF_DROIDGUARD_NETWORK_SERVER_URL = "droidguard_network_server_url" - } -} - diff --git a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt b/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt deleted file mode 100644 index db3f80cd..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.droidguard - -import android.content.Context -import android.os.Bundle -import android.util.Base64 -import com.android.volley.VolleyError -import com.android.volley.toolbox.StringRequest -import com.android.volley.toolbox.Volley -import org.microg.gms.checkin.LastCheckinInfo -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -interface DroidGuardResultCreator { - suspend fun getResult(flow: String, data: Map): ByteArray - - companion object { - fun getInstance(context: Context): DroidGuardResultCreator = when (DroidGuardPreferences(context).mode) { - DroidGuardPreferences.Mode.Disabled -> throw RuntimeException("DroidGuard disabled") - DroidGuardPreferences.Mode.Connector -> ConnectorDroidGuardResultCreator(context) - DroidGuardPreferences.Mode.Network -> NetworkDroidGuardResultCreator(context) - } - - suspend fun getResult(context: Context, flow: String, data: Map): ByteArray = - getInstance(context).getResult(flow, data) - } -} - -private class ConnectorDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { - override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> - Thread { - val bundle = Bundle() - for (entry in data) { - bundle.putString(entry.key, entry.value) - } - val conn = RemoteDroidGuardConnector(context) - val dg = conn.guard(flow, LastCheckinInfo.read(context).androidId.toString(), bundle) - if (dg == null) { - continuation.resumeWithException(RuntimeException("No DroidGuard result")) - } else if (dg.statusCode == 0 && dg.result != null) { - continuation.resume(dg.result) - } else { - continuation.resumeWithException(RuntimeException("Status: " + dg.statusCode + ", error:" + dg.errorMsg)) - } - }.start() - } -} - -private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { - private val queue = Volley.newRequestQueue(context) - private val url: String - get() = DroidGuardPreferences(context).networkServerUrl ?: throw RuntimeException("Network URL required") - - override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> - queue.add(PostParamsStringRequest("$url?flow=$flow", data, { - continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) - }, { - continuation.resumeWithException(RuntimeException(it)) - })) - } -} - -class PostParamsStringRequest(url: String, private val data: Map, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) { - override fun getParams(): Map = data -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt deleted file mode 100644 index c73dde72..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/droidguard/ServiceInfo.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.droidguard - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log -import java.io.Serializable -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.droidguard.SERVICE_INFO_REQUEST" -private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.droidguard.UPDATE_CONFIGURATION" -private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.droidguard.SERVICE_INFO_RESPONSE" -private const val EXTRA_SERVICE_INFO = "org.microg.gms.droidguard.SERVICE_INFO" -private const val EXTRA_CONFIGURATION = "org.microg.gms.droidguard.CONFIGURATION" -private const val TAG = "GmsGcmStatusInfo" - -data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable - -data class ServiceConfiguration(val mode: DroidGuardPreferences.Mode, val networkServerUrl: String?) : Serializable { - fun saveToPrefs(context: Context) { - DroidGuardPreferences(context).edit { - mode = this@ServiceConfiguration.mode - networkServerUrl = this@ServiceConfiguration.networkServerUrl - } - } -} - -private fun DroidGuardPreferences.toConfiguration(): ServiceConfiguration = ServiceConfiguration(mode, networkServerUrl) - -class ServiceInfoReceiver : BroadcastReceiver() { - private fun sendInfoResponse(context: Context) { - context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { - setPackage(context.packageName) - putExtra(EXTRA_SERVICE_INFO, ServiceInfo(DroidGuardPreferences(context).toConfiguration())) - }, null) - } - - override fun onReceive(context: Context, intent: Intent) { - try { - when (intent.action) { - ACTION_UPDATE_CONFIGURATION -> { - (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) - } - } - sendInfoResponse(context) - } catch (e: Exception) { - Log.w(TAG, e) - } - } -} - -private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { - context.registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - context.unregisterReceiver(this) - val serviceInfo = try { - intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo - } catch (e: Exception) { - it.resumeWithException(e) - return - } - try { - it.resume(serviceInfo) - } catch (e: Exception) { - Log.w(TAG, e) - } - } - }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) - try { - context.sendOrderedBroadcast(intent, null) - } catch (e: Exception) { - it.resumeWithException(e) - } -} - -suspend fun getDroidGuardServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_SERVICE_INFO_REQUEST - }, context) - -suspend fun setDroidGuardServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_UPDATE_CONFIGURATION - putExtra(EXTRA_CONFIGURATION, configuration) - }, context) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt b/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt index 3426fbb2..69e745f6 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt @@ -29,6 +29,7 @@ import org.microg.gms.BaseService import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.GmsService import org.microg.gms.common.PackageUtils +import org.microg.gms.droidguard.DroidGuardPreferences import org.microg.gms.droidguard.DroidGuardResultCreator import org.microg.gms.recaptcha.ReCaptchaActivity import org.microg.gms.recaptcha.appendUrlEncodedParam @@ -57,8 +58,15 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) return } + if (!SafetyNetPrefs.get(context).isEnabled) { - Log.d(TAG, "ignoring SafetyNet request, it's disabled") + Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") + callbacks.onAttestationData(Status.CANCELED, null) + return + } + + if (!DroidGuardPreferences.isEnabled(context)) { + Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled") callbacks.onAttestationData(Status.CANCELED, null) return } @@ -67,17 +75,12 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa try { val attestation = Attestation(context, packageName) attestation.buildPayload(nonce) - try { - val dg = DroidGuardResultCreator.getResult(context, "attest", mapOf("contentBinding" to attestation.payloadHashBase64)) - attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) - } catch (e: Exception) { - if (SafetyNetPrefs.get(context).isOfficial) throw e - Log.w(TAG, e) - null - } - val data = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) } - callbacks.onAttestationData(Status.SUCCESS, data) - } catch (e: IOException) { + val data = mapOf("contentBinding" to attestation.payloadHashBase64) + val dg = withContext(Dispatchers.IO) { DroidGuardResultCreator.getResult(context, "attest", data) } + attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) + val resultData = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) } + callbacks.onAttestationData(Status.SUCCESS, resultData) + } catch (e: Exception) { Log.w(TAG, e) callbacks.onAttestationData(Status.INTERNAL_ERROR, null) } @@ -112,11 +115,13 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) return } + if (!SafetyNetPrefs.get(context).isEnabled) { - Log.d(TAG, "ignoring SafetyNet request, it's disabled") + Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") callbacks.onAttestationData(Status.CANCELED, null) return } + val intent = Intent(context, ReCaptchaActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java b/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java index 02b93b0d..0294a5c0 100644 --- a/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java +++ b/play-services-droidguard-api/src/main/java/com/google/android/gms/droidguard/internal/DroidGuardInitReply.java @@ -9,11 +9,13 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -public class DroidGuardInitReply implements Parcelable { - public ParcelFileDescriptor pfd; - public Parcelable object; +import androidx.annotation.Nullable; - public DroidGuardInitReply(ParcelFileDescriptor pfd, Parcelable object) { +public class DroidGuardInitReply implements Parcelable { + public @Nullable ParcelFileDescriptor pfd; + public @Nullable Parcelable object; + + public DroidGuardInitReply(@Nullable ParcelFileDescriptor pfd, @Nullable Parcelable object) { this.pfd = pfd; this.object = object; } diff --git a/play-services-droidguard-core-proto/build.gradle b/play-services-droidguard-core-proto/build.gradle new file mode 100644 index 00000000..234ab40a --- /dev/null +++ b/play-services-droidguard-core-proto/build.gradle @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.squareup.wire' +apply plugin: 'kotlin' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + implementation "com.squareup.wire:wire-runtime:$wireVersion" +} + +wire { + kotlin {} +} + +sourceSets { + main.java.srcDirs += "$buildDir/generated/source/wire" +} + +compileKotlin { + kotlinOptions.jvmTarget = 1.8 +} + +compileTestKotlin { + kotlinOptions.jvmTarget = 1.8 +} + +apply from: '../gradle/publish-java.gradle' + +description = 'Protocol buffers for microG implementation of play-services-droidguard' diff --git a/play-services-droidguard-core-proto/src/main/proto/droidguard.proto b/play-services-droidguard-core-proto/src/main/proto/droidguard.proto new file mode 100644 index 00000000..bd2223ad --- /dev/null +++ b/play-services-droidguard-core-proto/src/main/proto/droidguard.proto @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2016, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +option java_package = "org.microg.gms.droidguard"; + +message Usage { + optional string flow = 1; + optional string packageName = 2; +} + +message KeyValuePair { + optional string key = 1; + optional string val = 2; +} + +message StackTraceElement { + optional string className = 1; + optional string methodName = 2; + optional string fileName = 3; + optional int32 lineNumber = 4; + optional bool isNativeMethod = 5; +} + +message ExceptionInfo { + optional string name = 1; + optional string message = 2; + repeated StackTraceElement stackTrace = 3; +} + +message ExceptionList { + repeated ExceptionInfo exceptions = 1; +} + +message PingData { + optional string field1 = 1; + optional int64 field2 = 2; +} + +message Request { + optional Usage usage = 1; + repeated KeyValuePair info = 2; + optional string versionName = 3; + optional bool hasAccount = 6; + optional bool isGoogleCn = 7; + optional bool enableInlineVm = 8; + repeated bytes cached = 9; + optional bytes field10 = 10; + optional int32 field11 = 11; + optional ExceptionList exceptions = 12; + optional int32 versionCode = 13; + optional string arch = 14; + optional int32 field15 = 15; + optional PingData ping = 16; +} + +message SignedResponse { + optional bytes data = 1; + optional bytes signature = 2; +} + +message Response { + optional bytes byteCode = 1; + optional string vmUrl = 2; + optional bytes vmChecksum = 3; + optional int32 expiryTimeSecs = 4; + optional bytes content = 5; + optional bool save = 6; + optional int32 minWait = 7; + optional int32 maxWait = 8; + optional bytes extra = 9; +} diff --git a/play-services-droidguard-core/build.gradle b/play-services-droidguard-core/build.gradle new file mode 100644 index 00000000..e96945d2 --- /dev/null +++ b/play-services-droidguard-core/build.gradle @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-droidguard-api') + + implementation project(':play-services-base-core') + implementation project(':play-services-chimera-core') + implementation project(':play-services-droidguard') + implementation project(':play-services-droidguard-core-proto') + implementation project(':play-services-tasks-ktx') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + + implementation "androidx.core:core-ktx:$coreVersion" + implementation "com.android.volley:volley:$volleyVersion" + implementation "com.squareup.wire:wire-runtime:$wireVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName "20.47.14" + versionCode 204714000 + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + buildConfigField("String", "VERSION_NAME", "\"${defaultConfig.versionName}\"") + buildConfigField("int", "VERSION_CODE", "${defaultConfig.versionCode}") + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-droidguard' diff --git a/play-services-droidguard-core/src/main/AndroidManifest.xml b/play-services-droidguard-core/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c38b66c4 --- /dev/null +++ b/play-services-droidguard-core/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java new file mode 100644 index 00000000..a8a56582 --- /dev/null +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.droidguard; + +import android.content.Intent; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.util.Base64; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.google.android.gms.framework.tracing.wrapper.TracingIntentService; + +import org.microg.gms.droidguard.DroidGuardServiceBroker; +import org.microg.gms.droidguard.GuardCallback; +import org.microg.gms.droidguard.HandleProxyFactory; +import org.microg.gms.droidguard.PingData; +import org.microg.gms.droidguard.Request; + +import java.util.Collections; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class DroidGuardChimeraService extends TracingIntentService { + public static final Object a = new Object(); + // factory + public HandleProxyFactory b; + // widevine + public Object c; + // executor + public Executor d; + // log + public Object e; + + private static final Object f = new Object(); + + // ping + private Object g; + // handler + private Handler h; + + + public DroidGuardChimeraService() { + super("DG"); + setIntentRedelivery(true); + } + + public DroidGuardChimeraService(HandleProxyFactory factory, Object ping, Object database) { + super("DG"); + setIntentRedelivery(true); + this.b = factory; + this.g = ping; + this.h = new Handler(); + } + + // fsc + private final void c(byte[] data) { + PingData ping = null; + if (data != null) { + Log.d("GmsGuardChimera", "c(" + Base64.encodeToString(data, Base64.NO_WRAP) + ")", new RuntimeException().fillInStackTrace()); + try { + ping = PingData.ADAPTER.decode(data); + } catch (Exception e) { + Log.w("GmsGuardChimera", e); + } + } else { + Log.d("GmsGuardChimera", "c(null)", new RuntimeException().fillInStackTrace()); + } + byte[] bytes = b.createPingHandle(getPackageName(), "full", b(""), ping).run(Collections.emptyMap()); + Log.d("GmsGuardChimera", "c.bytes = " + Base64.encodeToString(bytes, Base64.NO_WRAP)); + Request fastRequest = b.createRequest("fast", getPackageName(), null, bytes); + b.fetchFromServer("fast", fastRequest); + } + + // handle intent + public final void a(@Nullable Intent intent) { + Log.d("GmsGuardChimera", "a(" + intent + ")"); + if (intent != null && intent.getAction() != null && intent.getAction().equals("com.google.android.gms.droidguard.service.PING")) { + byte[] byteData = intent.getByteArrayExtra("data"); + if (byteData == null) { + int[] intData = intent.getIntArrayExtra("data"); + if (intData == null) { + c(null); + return; + } + byteData = new byte[intData.length]; + for (int i = 0; i < intData.length; i++) { + byteData[i] = (byte) intData[i]; + } + } + c(byteData); + } + } + + // getCallback + public final GuardCallback b(String packageName) { + Log.d("GmsGuardChimera", "b[getCallback](" + packageName + ")"); + return new GuardCallback(this, packageName); + } + + @Nullable + @Override + public final IBinder onBind(Intent intent) { + if (intent != null && intent.getAction() != null && intent.getAction().equals("com.google.android.gms.droidguard.service.START")) { + return new DroidGuardServiceBroker(this); + } + return null; + } + + @Override + public void onCreate() { + this.e = new Object(); + this.b = new HandleProxyFactory(this); + this.g = new Object(); + this.h = new Handler(); + this.c = new Object(); + this.d = new ThreadPoolExecutor(1, 1, 0, TimeUnit.NANOSECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy()); + super.onCreate(); + } +} diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java new file mode 100644 index 00000000..7d10c103 --- /dev/null +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.framework.tracing.wrapper; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.chimera.IntentService; + +import org.microg.gms.utils.PackageManagerWrapper; +import org.microg.gms.droidguard.VersionUtil; + +public abstract class TracingIntentService extends IntentService { + private static final String TAG = "TracingIntentService"; + + public TracingIntentService(String name) { + super(name); + } + + @Override + public void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + } + + protected abstract void a(@Nullable Intent intent); + + @Override + public PackageManager getPackageManager() { + return new PackageManagerWrapper(super.getPackageManager()) { + @NonNull + @Override + public PackageInfo getPackageInfo(@NonNull String packageName, int flags) { + PackageInfo packageInfo = super.getPackageInfo(packageName, flags); + if ("com.google.android.gms".equals(packageName)) { + VersionUtil versionUtil = new VersionUtil(TracingIntentService.this, new org.microg.gms.common.Build()); + packageInfo.versionCode = versionUtil.getVersionCode(); + packageInfo.versionName = versionUtil.getVersionString(); + packageInfo.sharedUserId = "com.google.uid.shared"; + } + return packageInfo; + } + }; + } + + @Override + public void onHandleIntent(@Nullable Intent intent) { + this.a(intent); + } +} diff --git a/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java new file mode 100644 index 00000000..4a3b4c2c --- /dev/null +++ b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard; + +import android.content.Context; +import android.media.MediaDrm; +import android.os.Build; +import android.util.Log; + +import org.microg.gms.settings.SettingsContract; + +import java.util.HashMap; + +/** + * Callbacks invoked from the DroidGuard VM + *

+ * We keep this file in Java to ensure ABI compatibility. + * Methods are invoked by name from within the VM and thus must keep current name. + */ +public class GuardCallback { + private static final String TAG = "GmsGuardCallback"; + private final Context context; + private final String packageName; + + public GuardCallback(Context context, String packageName) { + this.context = context; + this.packageName = packageName; + } + + public final String a(final byte[] array) { + Log.d(TAG, "a[?](" + array + ")"); + return new String(FallbackCreator.create(new HashMap<>(), array, "", context, null)); + } + + // getAndroidId + public final String b() { + try { + long androidId = SettingsContract.INSTANCE.getSettings(context, SettingsContract.CheckIn.INSTANCE.getContentUri(context), new String[]{SettingsContract.CheckIn.ANDROID_ID}, cursor -> cursor.getLong(0)); + Log.d(TAG, "b[getAndroidId]() = " + androidId); + return String.valueOf(androidId); + } catch (Throwable e) { + Log.w(TAG, "Failed to get Android ID, fallback to random", e); + } + long androidId = (long) (Math.random() * Long.MAX_VALUE); + Log.d(TAG, "b[getAndroidId]() = " + androidId + " (random)"); + return String.valueOf(androidId); + } + + // getPackageName + public final String c() { + Log.d(TAG, "c[getPackageName]() = " + packageName); + return packageName; + } + + // closeMediaDrmSession + public final void d(final Object mediaDrm, final byte[] sessionId) { + Log.d(TAG, "d[closeMediaDrmSession](" + mediaDrm + ", " + sessionId + ")"); + synchronized (MediaDrmLock.LOCK) { + if (Build.VERSION.SDK_INT >= 18) { + ((MediaDrm) mediaDrm).closeSession(sessionId); + } + } + } + + public final void e(final int task) { + Log.d(TAG, "e[?](" + task + ")"); + // TODO: Open database + if (task == 1) { + // TODO + } else if (task == 0) { + // TODO + } + // TODO: Set value in database + } +} diff --git a/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java new file mode 100644 index 00000000..4914cb78 --- /dev/null +++ b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/MediaDrmLock.java @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard; + +public class MediaDrmLock { + public static final Object LOCK = new Object(); +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt new file mode 100644 index 00000000..32721ee2 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +class BytesException : Exception { + val bytes: ByteArray + + constructor(bytes: ByteArray, message: String) : super(message) { + this.bytes = bytes + } + + constructor(bytes: ByteArray, cause: Throwable) : super(cause) { + this.bytes = bytes + } + + constructor(bytes: ByteArray, message: String, cause: Throwable) : super(message, cause) { + this.bytes = bytes + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt new file mode 100644 index 00000000..e1c4f491 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +/** + * - a: id + * - b: timestamp + * - c: seconds until expiry + * - d: vm key + * - e: ? + * - f: byte code + * - g: extra + */ +class DgDatabaseHelper(context: Context) : SQLiteOpenHelper(context, "dg.db", null, 2) { + override fun onCreate(db: SQLiteDatabase) { + // Note: "NON NULL" is actually not a valid sqlite constraint, but this is what we see in the original database 🤷 + db.execSQL("CREATE TABLE main (a TEXT NOT NULL, b LONG NOT NULL, c LONG NOT NULL, d TEXT NON NULL, e TEXT NON NULL,f BLOB NOT NULL,g BLOB NOT NULL);"); + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("DROP TABLE IF EXISTS main;"); + this.onCreate(db); + } + + /** + * @return vm key, byte code, extra + */ + fun get(id: String): Triple? = readableDatabase.use { db -> + val time = System.currentTimeMillis() / 1000 + db.query("main", arrayOf("f", "d", "e", "c", "g"), "a = ? AND b <= $time AND $time < (b + c)", arrayOf(id), null, null, "b DESC", "1").use { + if (it.moveToNext()) { + Triple(it.getString(1), it.getBlob(0), it.getBlob(4)) + } else { + null + } + } + } + + fun put(id: String, expiry: Long, vmKey: String, byteCode: ByteArray, extra: ByteArray) { + val dbData = ContentValues().apply { + put("a", id) + put("b", System.currentTimeMillis() / 1000) + put("c", expiry) + put("d", vmKey) + put("e", "") + put("f", byteCode) + put("g", extra) + } + writableDatabase.use { + it.beginTransaction() + if (expiry <= 0) { + it.delete("main", "a = ?", arrayOf(id)) + } else if (it.update("main", dbData, "a = ?", arrayOf(id)) <= 0) { + it.insert("main", null, dbData) + } + it.setTransactionSuccessful() + it.endTransaction() + } + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt new file mode 100644 index 00000000..269de569 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper + +class DgpDatabaseHelper(context: Context) : SQLiteOpenHelper(context, "dgp.db", null, 1) { + override fun onCreate(db: SQLiteDatabase) { + db.execSQL("CREATE TABLE t (a BLOB NOT NULL);"); + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt new file mode 100644 index 00000000..fff942c1 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.annotation.SuppressLint +import android.content.Context +import android.os.ConditionVariable +import android.os.ParcelFileDescriptor +import android.os.Parcelable +import android.util.Log +import com.google.android.gms.droidguard.internal.DroidGuardInitReply +import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest +import com.google.android.gms.droidguard.internal.IDroidGuardHandle +import java.io.FileNotFoundException + +class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: HandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() { + private val condition = ConditionVariable() + + private var flow: String? = null + private var handleProxy: HandleProxy? = null + private var handleInitError: Throwable? = null + + override fun init(flow: String?) { + Log.d(TAG, "init($flow)") + initWithRequest(flow, null) + } + + @SuppressLint("SetWorldReadable") + override fun initWithRequest(flow: String?, request: DroidGuardResultsRequest?): DroidGuardInitReply { + Log.d(TAG, "initWithRequest($flow)") + this.flow = flow + try { + var handleProxy: HandleProxy? = null + if (flow !in NOT_LOW_LATENCY_FLOWS) { + try { + handleProxy = factory.createLowLatencyHandle(flow, callback, request) + Log.d(TAG, "Using low-latency handle") + } catch (e: Exception) { + Log.w(TAG, e) + } + } + if (handleProxy == null) { + handleProxy = factory.createHandle(packageName, flow, callback, request) + } + if (handleProxy.init()) { + this.handleProxy = handleProxy + } else { + throw Exception("init failed") + } + } catch (e: Throwable) { + Log.w(TAG, "Error during handle init", e) + handleInitError = e + } + condition.open() + if (handleInitError == null) { + try { + handleProxy?.let { handleProxy -> + val `object` = handleProxy.handle.javaClass.getDeclaredMethod("rb").invoke(handleProxy.handle) as? Parcelable? + if (`object` != null) { + val vmKey = handleProxy.vmKey + val theApk = factory.getTheApkFile(vmKey) + try { + theApk.setReadable(true, false) + return DroidGuardInitReply(ParcelFileDescriptor.open(theApk, ParcelFileDescriptor.MODE_READ_ONLY), `object`) + } catch (e: FileNotFoundException) { + throw Exception("Files for VM $vmKey not found on disk") + } + } + } + } catch (e: Exception) { + this.handleProxy = null + handleInitError = e + } + } + return DroidGuardInitReply(null, null) + } + + override fun guard(map: MutableMap): ByteArray { + Log.d(TAG, "guard()") + handleInitError?.let { return FallbackCreator.create(flow, context, map, it) } + val handleProxy = this.handleProxy ?: return FallbackCreator.create(flow, context, map, IllegalStateException()) + return try { + handleProxy.handle::class.java.getDeclaredMethod("ss", Map::class.java).invoke(handleProxy.handle, map) as ByteArray + } catch (e: Exception) { + try { + throw BytesException(handleProxy.extra, e) + } catch (e2: Exception) { + FallbackCreator.create(flow, context, map, e2) + } + } + } + + override fun close() { + Log.d(TAG, "close()") + condition.block() + try { + handleProxy?.close() + } catch (e: Exception) { + Log.w(TAG, "Error during handle close", e) + } + handleProxy = null + handleInitError = null + } + + companion object { + private const val TAG = "GmsGuardHandleImpl" + private val NOT_LOW_LATENCY_FLOWS = setOf("ad_attest", "attest", "checkin", "federatedMachineLearningReduced", "msa-f", "ad-event-attest-token") + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt new file mode 100644 index 00000000..d038c53c --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.database.Cursor +import androidx.core.database.getStringOrNull +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.DroidGuard.ENABLED +import org.microg.gms.settings.SettingsContract.DroidGuard.MODE +import org.microg.gms.settings.SettingsContract.DroidGuard.NETWORK_SERVER_URL + +object DroidGuardPreferences { + + private fun getSettings(context: Context, projection: String, def: T, f: (Cursor) -> T): T { + return try { + SettingsContract.getSettings(context, SettingsContract.DroidGuard.getContentUri(context), arrayOf(projection), f) + } catch (e: Exception) { + def + } + } + + @JvmStatic + fun isEnabled(context: Context): Boolean = true //getSettings(context, ENABLED, false) { it.getInt(0) != 0 } + + @JvmStatic + fun getMode(context: Context): Mode = getSettings(context, MODE, Mode.Embedded) { c -> Mode.valueOf(c.getString(0)) } + + @JvmStatic + fun getNetworkServerUrl(context: Context): String? = getSettings(context, NETWORK_SERVER_URL, null) { c -> c.getStringOrNull(0) } + + enum class Mode { + Embedded, + Network + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt new file mode 100644 index 00000000..68e2bee3 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.util.Base64 +import com.android.volley.VolleyError +import com.android.volley.toolbox.StringRequest +import com.android.volley.toolbox.Volley +import com.google.android.gms.tasks.await +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +interface DroidGuardResultCreator { + suspend fun getResult(flow: String, data: Map): ByteArray + + companion object { + fun getInstance(context: Context): DroidGuardResultCreator = + if (DroidGuardPreferences.isEnabled(context)) { + when (DroidGuardPreferences.getMode(context)) { + DroidGuardPreferences.Mode.Embedded -> EmbeddedDroidGuardResultCreator(context) + DroidGuardPreferences.Mode.Network -> NetworkDroidGuardResultCreator(context) + } + } else { + throw RuntimeException("DroidGuard disabled") + } + + suspend fun getResult(context: Context, flow: String, data: Map): ByteArray = + getInstance(context).getResult(flow, data) + } +} + +private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { + private val queue = Volley.newRequestQueue(context) + private val url: String + get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw RuntimeException("Network URL required") + + override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> + queue.add(PostParamsStringRequest("$url?flow=$flow", data, { + continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) + }, { + continuation.resumeWithException(RuntimeException(it)) + })) + } + + companion object { + class PostParamsStringRequest(url: String, private val data: Map, listener: (String) -> Unit, errorListener: (VolleyError) -> Unit) : StringRequest(Method.POST, url, listener, errorListener) { + override fun getParams(): Map = data + } + } +} + +private class EmbeddedDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { + private val client: DroidGuardClient by lazy { DroidGuardClientImpl(context) } + override suspend fun getResult(flow: String, data: Map): ByteArray { + val handle = client.getHandle().await() + try { + handle.init(flow) + return handle.guard(data) + } finally { + try { + handle.close() + } catch (e: Exception) { + // ignore + } + } + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt new file mode 100644 index 00000000..e68359c6 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.droidguard + +import com.google.android.gms.droidguard.DroidGuardChimeraService +import org.microg.gms.chimera.ServiceLoader +import org.microg.gms.chimera.ServiceProxy + +class DroidGuardService : ServiceProxy(ServiceLoader.static()) diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt new file mode 100644 index 00000000..37dd4724 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.droidguard.DroidGuardChimeraService +import org.microg.gms.AbstractGmsServiceBroker +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import java.util.* + +class DroidGuardServiceBroker(val service: DroidGuardChimeraService) : AbstractGmsServiceBroker(EnumSet.of(GmsService.DROIDGUARD)) { + + override fun getService(callback: IGmsCallbacks?, request: GetServiceRequest?) { + handleServiceRequest(callback, request, null) + } + + override fun handleServiceRequest(callback: IGmsCallbacks?, request: GetServiceRequest?, service: GmsService?) { + val packageName = PackageUtils.getAndCheckCallingPackageOrExtendedAccess(this.service, request!!.packageName) + callback!!.onPostInitComplete(0, DroidGuardServiceImpl(this.service, packageName!!), null) + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt new file mode 100644 index 00000000..b05b170e --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.util.Log +import com.google.android.gms.droidguard.DroidGuardChimeraService +import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest +import com.google.android.gms.droidguard.internal.IDroidGuardCallbacks +import com.google.android.gms.droidguard.internal.IDroidGuardHandle +import com.google.android.gms.droidguard.internal.IDroidGuardService + +class DroidGuardServiceImpl(private val service: DroidGuardChimeraService, private val packageName: String) : IDroidGuardService.Stub() { + override fun guard(callbacks: IDroidGuardCallbacks?, flow: String?, map: MutableMap?) { + Log.d(TAG, "guard()") + guardWithRequest(callbacks, flow, map, null) + } + + override fun guardWithRequest(callbacks: IDroidGuardCallbacks?, flow: String?, map: MutableMap?, request: DroidGuardResultsRequest?) { + Log.d(TAG, "guardWithRequest()") + TODO("Not yet implemented") + } + + override fun getHandle(): IDroidGuardHandle { + Log.d(TAG, "getHandle()") + return DroidGuardHandleImpl(service, packageName, service.b, service.b(packageName)) + } + + override fun getClientTimeoutMillis(): Int { + Log.d(TAG, "getClientTimeoutMillis()") + return 60000 + } + + companion object { + const val TAG = "GmsGuardServiceImpl" + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt new file mode 100644 index 00000000..fb648c2f --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.util.Log + +object FallbackCreator { + private val FAST_FAIL = setOf("ad_attest", "recaptcha-frame", "federatedMachineLearningReduced", "msa-f", "ad-event-attest-token") + + @JvmStatic + fun create(flow: String?, context: Context, map: Map, e: Throwable): ByteArray { + Log.w("DGFallback", "create($flow)") + return if (flow in FAST_FAIL) { + "ERROR : no fallback for $flow".encodeToByteArray() + } else { + try { + create(map, null, flow, context, e) + } catch (e: Throwable) { + Log.w("DGFallback", e) + "ERROR : $e".encodeToByteArray() + } + } + } + + @JvmStatic + fun create(map: Map, bytes: ByteArray?, flow: String?, context: Context, e: Throwable): ByteArray { + TODO("Not yet implemented") + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt new file mode 100644 index 00000000..e1147996 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import android.os.Bundle +import android.os.Parcelable + +class HandleProxy(val handle: Any, val vmKey: String, val extra: ByteArray = ByteArray(0)) { + constructor(clazz: Class<*>, context: Context, vmKey: String, data: Parcelable) : this( + kotlin.runCatching { + clazz.getDeclaredConstructor(Context::class.java, Parcelable::class.java).newInstance(context, data) + }.getOrElse { + throw BytesException(ByteArray(0), it) + }, + vmKey + ) + + constructor(clazz: Class<*>, context: Context, flow: String?, byteCode: ByteArray, callback: Any, vmKey: String, extra: ByteArray, bundle: Bundle?) : this( + kotlin.runCatching { + clazz.getDeclaredConstructor(Context::class.java, String::class.java, ByteArray::class.java, Object::class.java, Bundle::class.java).newInstance(context, flow, byteCode, callback, bundle) + }.getOrElse { + throw BytesException(extra, it) + }, vmKey, extra) + + fun run(data: Map): ByteArray { + try { + return handle.javaClass.getDeclaredMethod("run", Map::class.java).invoke(handle, data) as ByteArray + } catch (e: Exception) { + throw BytesException(extra, e) + } + } + + fun init(): Boolean { + try { + return handle.javaClass.getDeclaredMethod("init").invoke(handle) as Boolean + } catch (e: Exception) { + throw BytesException(extra, e) + } + } + + fun close() { + try { + handle.javaClass.getDeclaredMethod("close").invoke(handle) + } catch (e: Exception) { + throw BytesException(extra, e) + } + } + +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt new file mode 100644 index 00000000..a8fe931b --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import com.android.volley.NetworkResponse +import com.android.volley.VolleyError +import com.android.volley.toolbox.RequestFuture +import com.android.volley.toolbox.Volley +import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest +import dalvik.system.DexClassLoader +import okio.ByteString.Companion.decodeHex +import okio.ByteString.Companion.of +import org.microg.gms.common.Build +import org.microg.gms.droidguard.core.BuildConfig +import java.io.File +import java.io.IOException +import java.security.MessageDigest +import java.security.cert.Certificate +import java.util.* +import com.android.volley.Request as VolleyRequest +import com.android.volley.Response as VolleyResponse + +class HandleProxyFactory(private val context: Context) { + private val build: Build = Build() + private val classMap = hashMapOf>() + private val dgDb: DgDatabaseHelper = DgDatabaseHelper(context) + private val version = VersionUtil(context, build) + private val queue = Volley.newRequestQueue(context) + + fun createHandle(packageName: String, flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + val (vmKey, byteCode, bytes) = readFromDatabase(flow) ?: fetchFromServer(flow, packageName) + return createHandleProxy(flow, vmKey, byteCode, bytes, callback, request) + } + + fun createPingHandle(packageName: String, flow: String, callback: GuardCallback, pingData: PingData?): HandleProxy { + val (vmKey, byteCode, bytes) = fetchFromServer(flow, createRequest(flow, packageName, pingData)) + return createHandleProxy(flow, vmKey, byteCode, bytes, callback, DroidGuardResultsRequest().also { it.clientVersion = 0 }) + } + + fun createLowLatencyHandle(flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + val (vmKey, byteCode, bytes) = readFromDatabase("fast") ?: throw Exception("low latency (fast) flow not available") + return createHandleProxy(flow, vmKey, byteCode, bytes, callback, request) + } + + fun SignedResponse.unpack(): Response { + if (SignatureVerifier.verifySignature(data!!.toByteArray(), signature!!.toByteArray())) { + return Response.ADAPTER.decode(data!!) + } else { + throw SecurityException("Signature invalid") + } + } + + private fun readFromDatabase(flow: String?): Triple? { + val id = "$flow/${version.versionString}/${build.fingerprint}" + return dgDb.get(id) + } + + fun createRequest(flow: String?, packageName: String, pingData: PingData? = null, extra: ByteArray? = null): Request { + return Request( + usage = Usage(flow, packageName), + info = listOf( + KeyValuePair("BOARD", build.board), + KeyValuePair("BOOTLOADER", build.bootloader), + KeyValuePair("BRAND", build.brand), + KeyValuePair("CPU_ABI", build.cpu_abi), + KeyValuePair("CPU_ABI2", build.cpu_abi2), + KeyValuePair("SUPPORTED_ABIS", build.supported_abis.joinToString(",")), + KeyValuePair("DEVICE", build.device), + KeyValuePair("DISPLAY", build.display), + KeyValuePair("FINGERPRINT", build.fingerprint), + KeyValuePair("HARDWARE", build.hardware), + KeyValuePair("HOST", build.host), + KeyValuePair("ID", build.id), + KeyValuePair("MANUFACTURER", build.manufacturer), + KeyValuePair("MODEL", build.model), + KeyValuePair("PRODUCT", build.product), + KeyValuePair("RADIO", build.radio), + KeyValuePair("SERIAL", build.serial), + KeyValuePair("TAGS", build.tags), + KeyValuePair("TIME", build.time.toString()), + KeyValuePair("TYPE", build.type), + KeyValuePair("USER", build.user), + KeyValuePair("VERSION.CODENAME", build.version_codename), + KeyValuePair("VERSION.INCREMENTAL", build.version_incremental), + KeyValuePair("VERSION.RELEASE", build.version_release), + KeyValuePair("VERSION.SDK", build.version_sdk), + KeyValuePair("VERSION.SDK_INT", build.version_sdk_int.toString()), + ), + versionName = version.versionString, + versionCode = BuildConfig.VERSION_CODE, + hasAccount = false, + isGoogleCn = false, + enableInlineVm = true, + cached = getCacheDir().list()?.map { it.decodeHex() }.orEmpty(), + arch = System.getProperty("os.arch"), + ping = pingData, + field10 = extra?.let { of(*it) }, + ) + } + + fun fetchFromServer(flow: String?, packageName: String): Triple { + return fetchFromServer(flow, createRequest(flow, packageName)) + } + + fun fetchFromServer(flow: String?, request: Request): Triple { + val future = RequestFuture.newFuture() + queue.add(object : VolleyRequest(Method.POST, SERVER_URL, future) { + override fun parseNetworkResponse(response: NetworkResponse): VolleyResponse { + return try { + VolleyResponse.success(SignedResponse.ADAPTER.decode(response.data), null) + } catch (e: Exception) { + VolleyResponse.error(VolleyError(e)) + } + } + + override fun deliverResponse(response: SignedResponse) { + future.onResponse(response) + } + + override fun getBody(): ByteArray = request.encode() + + override fun getBodyContentType(): String = "application/x-protobuf" + + override fun getHeaders(): Map { + return mapOf( + "User-Agent" to "DroidGuard/${version.versionCode}" + ) + } + }) + val signed: SignedResponse = future.get() + val response = signed.unpack() + val vmKey = response.vmChecksum!!.hex() + if (!isValidCache(vmKey)) { + val temp = File(getCacheDir(), "${UUID.randomUUID()}.apk") + temp.parentFile!!.mkdirs() + temp.writeBytes(response.content!!.toByteArray()) + getOptDir(vmKey).mkdirs() + temp.renameTo(getTheApkFile(vmKey)) + updateCacheTimestamp(vmKey) + if (!isValidCache(vmKey)) { + getCacheDir(vmKey).deleteRecursively() + throw IllegalStateException() + } + } + val id = "$flow/${version.versionString}/${build.fingerprint}" + val expiry = (response.expiryTimeSecs ?: 0).toLong() + val byteCode = response.byteCode!!.toByteArray() + val extra = response.extra!!.toByteArray() + dgDb.put(id, expiry, vmKey, byteCode, extra) + return Triple(vmKey, byteCode, extra) + } + + private fun createHandleProxy(flow: String?, vmKey: String, byteCode: ByteArray, extra: ByteArray, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + val clazz = loadClass(vmKey, extra) + return HandleProxy(clazz, context, flow, byteCode, callback, vmKey, extra, request?.bundle) + } + + fun getTheApkFile(vmKey: String) = File(getCacheDir(vmKey), "the.apk") + private fun getCacheDir() = context.getDir(CACHE_FOLDER_NAME, Context.MODE_PRIVATE) + private fun getCacheDir(vmKey: String) = File(getCacheDir(), vmKey) + private fun getOptDir(vmKey: String) = File(getCacheDir(vmKey), "opt") + private fun isValidCache(vmKey: String) = getTheApkFile(vmKey).isFile && getOptDir(vmKey).isDirectory + + private fun updateCacheTimestamp(vmKey: String) { + try { + val timestampFile = File(getCacheDir(vmKey), "t") + if (!timestampFile.exists() && !timestampFile.createNewFile()) { + throw Exception("Failed to touch last-used file for $vmKey.") + } + if (!timestampFile.setLastModified(System.currentTimeMillis())) { + throw Exception("Failed to update last-used timestamp for $vmKey.") + } + } catch (e: IOException) { + throw Exception("Failed to touch last-used file for $vmKey.") + } + } + + private fun verifyApkSignature(apk: File): Boolean { + return true + val certificates: Array = TODO() + if (certificates.size != 1) return false + return Arrays.equals(MessageDigest.getInstance("SHA-256").digest(certificates[0].encoded), PROD_CERT_HASH) + } + + private fun loadClass(vmKey: String, bytes: ByteArray): Class<*> { + val clazz = classMap[vmKey] + if (clazz != null) { + updateCacheTimestamp(vmKey) + return clazz + } else { + if (!isValidCache(vmKey)) { + throw BytesException(bytes, "VM key $vmKey not found in cache") + } + if (!verifyApkSignature(getTheApkFile(vmKey))) { + getCacheDir(vmKey).deleteRecursively() + throw ClassNotFoundException("APK signature verification failed") + } + val loader = DexClassLoader(getTheApkFile(vmKey).absolutePath, getOptDir(vmKey).absolutePath, null, context.classLoader) + val clazz = loader.loadClass(CLASS_NAME) + classMap[vmKey] = clazz + return clazz + } + } + + companion object { + const val CLASS_NAME = "com.google.ccc.abuse.droidguard.DroidGuard" + const val SERVER_URL = "https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI" + const val CACHE_FOLDER_NAME = "cache_dg" + val PROD_CERT_HASH = byteArrayOf(61, 122, 18, 35, 1, -102, -93, -99, -98, -96, -29, 67, 106, -73, -64, -119, 107, -5, 79, -74, 121, -12, -34, 95, -25, -62, 63, 50, 108, -113, -103, 74) + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt new file mode 100644 index 00000000..5c8b8460 --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.util.Base64 +import android.util.Log +import java.security.KeyFactory +import java.security.Signature +import java.security.spec.X509EncodedKeySpec + +object SignatureVerifier { + const val TAG = "GmsGuardSigVerify" + const val PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxW77dCKJ8mhEIfXXdeidi7/7LMNM/fzwI+wj1Ed8xIKgTYWCnekRko3JxQb4Cv/gEL5hEA8e9lFs3V67VUL6hCo1FxysXj7Q8n3Kp7hARDkbiZ0mdk8bSanqrPAXTPx6pEL2ZOzfFCHEtJdhz5Ozp2C4XTKF1SBv/YbpsqSUJwdhG7ZPGjyCMRloMww6ITpGdVQ8lChklkCek0WPbz2UrY5RC1qIJKmmcB6KNxxE776Dn6QoYbhN5jPeVBp7lDD3UxjfVzTxKKDAome6fUVBop3dpcLM6rq3+nNT2YArgqTD1qtsVM9vHlcLaAYaPg82vtIN80iDUseMlVHgK+nf6wIDAQAB" + + fun verifySignature(data: ByteArray, signature: ByteArray): Boolean { + try { + val keyFactory = KeyFactory.getInstance("RSA") ?: return false + val sig = Signature.getInstance("SHA256withRSA") ?: return false + val keySpec = X509EncodedKeySpec(Base64.decode(PUBLIC_KEY, 0)) + sig.initVerify(keyFactory.generatePublic(keySpec)) + sig.update(data) + return sig.verify(signature) + } catch (e: Exception) { + Log.w(TAG, e) + return false + } + } +} diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt new file mode 100644 index 00000000..c9a4b35f --- /dev/null +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard + +import android.content.Context +import org.microg.gms.common.Build +import org.microg.gms.droidguard.core.BuildConfig + +class VersionUtil(private val context: Context, private val build: Build = Build()) { + val buildType: String + get() { + // Note: Android TV and Watch use different version codes + val versionCode = when (build.version_sdk_int) { + 31 -> "19" + 30 -> "15" + 29 -> "12" + 28 -> "10" + 23, 24, 25, 26, 27 -> "04" + 21, 22 -> "02" + else -> "00" + } + val architectureCode = when (build.cpu_abi) { + "x86_64" -> "08" + "x86" -> "07" + "arm64-v8a" -> "04" + "arm", "armeabi", "armeabi-v7a" -> "03" + else -> "00" + } + val dpiCode = when (context.resources.displayMetrics.densityDpi) { + 160 -> "02" + 240 -> "04" + 320 -> "06" + 480 -> "08" + else -> "00" + } + val type = "$versionCode$architectureCode$dpiCode" + if (isKnown(type)) return type + val nodpi = "$versionCode${architectureCode}00" + if (isKnown(nodpi)) return nodpi // Fallback to nodpi for increased compat + return type // Use unknown build type + } + val versionString: String + get() = "${BuildConfig.VERSION_NAME} ($buildType-{{cl}})" + val versionCode: Int + get() = BuildConfig.VERSION_CODE + (getVersionOffset(buildType) ?: 0) + + fun isKnown(type: String): Boolean = getVersionOffset(type) != null + + fun getVersionOffset(type: String): Int? { + val v1 = type.substring(0, 2) + val v2 = type.substring(2, 4) + val v3 = type.substring(4, 6) + val i1 = BUILD_MAP.indexOfFirst { it.first == v1 }.takeIf { it >= 0 } ?: return null + val i2 = BUILD_MAP[i1].second.indexOfFirst { it.first == v2 }.takeIf { it >= 0 } ?: return null + val i3 = BUILD_MAP[i1].second[i2].second.indexOf(v3).takeIf { it > 0 } ?: return null + val o1 = BUILD_MAP.subList(0, i1).map { it.second.map { it.second.size }.sum() }.sum() + val o2 = BUILD_MAP[i1].second.subList(0, i2).map { it.second.size }.sum() + return o1 + o2 + i3 + } + + companion object { + val BUILD_MAP = listOf( + "00" to listOf("03" to listOf("00", "02", "04", "06", "08"), "07" to listOf("00")), + "02" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "04" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "10" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "12" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "15" to listOf("03" to listOf("00", "04", "06", "08"), "04" to listOf("00", "06", "08"), "07" to listOf("00"), "08" to listOf("00")), + "19" to listOf("03" to listOf("00", "08"), "04" to listOf("00", "08"), "07" to listOf("00"), "08" to listOf("00")), + ) + } +} diff --git a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt index adcaa45b..a5b8f3d4 100644 --- a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt +++ b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClient.kt @@ -5,7 +5,6 @@ package org.microg.gms.droidguard -import com.google.android.gms.droidguard.internal.IDroidGuardHandle import com.google.android.gms.tasks.Task interface DroidGuardClient { diff --git a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt index 49f3fc63..d8af0b6d 100644 --- a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt +++ b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardClientImpl.kt @@ -11,14 +11,13 @@ import com.google.android.gms.common.api.Api import com.google.android.gms.common.api.Api.ApiOptions.NoOptions import com.google.android.gms.common.api.GoogleApi import com.google.android.gms.tasks.Task -import org.microg.gms.common.api.ApiClientBuilder import org.microg.gms.common.api.ApiClientSettings import org.microg.gms.common.api.ConnectionCallbacks import org.microg.gms.common.api.OnConnectionFailedListener class DroidGuardClientImpl(context: Context) : GoogleApi(context, API), DroidGuardClient { companion object { - private val API = Api(ApiClientBuilder { _: NoOptions?, context: Context, _: Looper?, _: ApiClientSettings?, callbacks: ConnectionCallbacks, connectionFailedListener: OnConnectionFailedListener -> DroidGuardApiClient(context, callbacks, connectionFailedListener) }) + private val API = Api { _: NoOptions?, context: Context, _: Looper?, _: ApiClientSettings?, callbacks: ConnectionCallbacks, connectionFailedListener: OnConnectionFailedListener -> DroidGuardApiClient(context, callbacks, connectionFailedListener) } } override fun getHandle(): Task { diff --git a/settings.gradle b/settings.gradle index bda5a7e1..601c7f71 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include ':firebase-dynamic-links-api' // core only include ':play-services-core-proto' +include ':play-services-droidguard-core-proto' include ':play-services-nearby-core-proto' include ':play-services-wearable-proto' @@ -46,6 +47,7 @@ include ':play-services-base-core' include ':play-services-chimera-core' include ':play-services-conscrypt-provider-core' include ':play-services-cronet-core' +include ':play-services-droidguard-core' include ':play-services-location-core' include ':play-services-maps-core-mapbox' include ':play-services-maps-core-vtm' From a7b2b7e3f8fce0b9ca646733cf2b162fed091e87 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 24 Nov 2021 23:39:33 -0600 Subject: [PATCH 28/63] Add profile manager --- .../java/org/microg/gms/common/Build.java | 62 ----- .../java/org/microg/gms/common/Utils.java | 4 - .../kotlin/org/microg/gms/profile/Build.kt | 94 +++++++ .../org/microg/gms/profile/ProfileManager.kt | 231 ++++++++++++++++++ .../microg/gms/settings/SettingsContract.kt | 14 ++ .../microg/gms/settings/SettingsProvider.kt | 24 ++ .../java/org/microg/gms/auth/AuthRequest.java | 14 +- .../org/microg/gms/checkin/CheckinClient.java | 31 +-- .../microg/gms/checkin/CheckinManager.java | 2 +- .../microg/gms/gcm/PushRegisterManager.java | 2 +- .../org/microg/gms/gcm/RegisterRequest.java | 11 +- .../org/microg/gms/safetynet/Attestation.java | 7 +- .../microg/gms/wearable/MessageHandler.java | 4 +- .../org/microg/gms/gcm/PushRegisterService.kt | 6 +- .../src/main/res/xml/profile_bullhead_27.xml | 35 +++ .../tracing/wrapper/TracingIntentService.java | 2 +- .../gms/droidguard/HandleProxyFactory.kt | 73 +++--- .../org/microg/gms/droidguard/VersionUtil.kt | 12 +- 18 files changed, 488 insertions(+), 140 deletions(-) delete mode 100644 play-services-base-core/src/main/java/org/microg/gms/common/Build.java create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt create mode 100644 play-services-core/src/main/res/xml/profile_bullhead_27.xml diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/Build.java b/play-services-base-core/src/main/java/org/microg/gms/common/Build.java deleted file mode 100644 index c297c295..00000000 --- a/play-services-base-core/src/main/java/org/microg/gms/common/Build.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package org.microg.gms.common; - -import android.annotation.TargetApi; - -import java.util.Locale; -import java.util.Random; - -// TODO: Make flexible -public class Build { - public String board = android.os.Build.BOARD; - public String bootloader = android.os.Build.BOOTLOADER; - public String brand = android.os.Build.BRAND; - public String cpu_abi = android.os.Build.CPU_ABI; - public String cpu_abi2 = android.os.Build.CPU_ABI2; - @TargetApi(21) - public String[] supported_abis = android.os.Build.VERSION.SDK_INT >= 21 ? android.os.Build.SUPPORTED_ABIS : new String[0]; - public String device = android.os.Build.DEVICE; - public String display = android.os.Build.DISPLAY; - public String fingerprint = android.os.Build.FINGERPRINT; - public String hardware = android.os.Build.HARDWARE; - public String host = android.os.Build.HOST; - public String id = android.os.Build.ID; - public String manufacturer = android.os.Build.MANUFACTURER; - public String model = android.os.Build.MODEL; - public String product = android.os.Build.PRODUCT; - public String radio = android.os.Build.RADIO; - public String serial = generateSerialNumber(); // TODO: static - public String tags = android.os.Build.TAGS; - public long time = android.os.Build.TIME; - public String type = android.os.Build.TYPE; - public String user = android.os.Build.USER; - public String version_codename = android.os.Build.VERSION.CODENAME; - public String version_incremental = android.os.Build.VERSION.INCREMENTAL; - public String version_release = android.os.Build.VERSION.RELEASE; - public String version_sdk = android.os.Build.VERSION.SDK; - public int version_sdk_int = android.os.Build.VERSION.SDK_INT; - - private String generateSerialNumber() { - String serial = "008741"; - Random rand = new Random(); - for (int i = 0; i < 10; i++) - serial += Integer.toString(rand.nextInt(16), 16); - serial = serial.toUpperCase(Locale.US); - return serial; - } -} diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/Utils.java b/play-services-base-core/src/main/java/org/microg/gms/common/Utils.java index c965a444..5e5dfd7a 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/Utils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/Utils.java @@ -33,10 +33,6 @@ public class Utils { return Locale.getDefault(); // TODO } - public static Build getBuild(Context context) { - return new Build(); - } - public static DeviceIdentifier getDeviceIdentifier(Context context) { return new DeviceIdentifier(); } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt new file mode 100644 index 00000000..c3cb8853 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.profile + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import kotlin.random.Random + +object Build { + @JvmField + var BOARD: String? = null + + @JvmField + var BOOTLOADER: String? = null + + @JvmField + var BRAND: String? = null + + @JvmField + var CPU_ABI: String? = null + + @JvmField + var CPU_ABI2: String? = null + + @JvmField + @TargetApi(21) + var SUPPORTED_ABIS: Array = emptyArray() + + @JvmField + var DEVICE: String? = null + + @JvmField + var DISPLAY: String? = null + + @JvmField + var FINGERPRINT: String? = null + + @JvmField + var HARDWARE: String? = null + + @JvmField + var HOST: String? = null + + @JvmField + var ID: String? = null + + @JvmField + var MANUFACTURER: String? = null + + @JvmField + var MODEL: String? = null + + @JvmField + var PRODUCT: String? = null + + @JvmField + var RADIO: String? = null + + @JvmField + var SERIAL: String? = null + + @JvmField + var TAGS: String? = null + + @JvmField + var TIME: Long = 0L + + @JvmField + var TYPE: String? = null + + @JvmField + var USER: String? = null + + object VERSION { + @JvmField + var CODENAME: String? = null + + @JvmField + var INCREMENTAL: String? = null + + @JvmField + var RELEASE: String? = null + + @JvmField + var SDK: String? = null + + @JvmField + var SDK_INT: Int = 0 + } +} 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 new file mode 100644 index 00000000..493f8bd0 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt @@ -0,0 +1,231 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.profile + +import android.annotation.SuppressLint +import android.content.ContentValues +import android.content.Context +import android.util.Log +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.Profile +import org.xmlpull.v1.XmlPullParser +import kotlin.random.Random + +object ProfileManager { + private const val TAG = "ProfileManager" + const val PROFILE_REAL = "real" + const val PROFILE_AUTO = "auto" + const val PROFILE_NATIVE = "native" + + private var initialized = false + + private fun getProfileFromSettings(context: Context) = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } + private fun getAutoProfile(context: Context): String { + val profile = "${android.os.Build.DEVICE}_${android.os.Build.VERSION.SDK_INT}" + if (hasProfile(context, profile)) return profile + return PROFILE_NATIVE + } + + private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile", null, null) + private fun hasProfile(context: Context, profile: String): Boolean = getProfileResId(context, profile) != 0 + private fun getProfileData(context: Context, profile: String, realData: Map): Map? { + try { + if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return null + val profileResId = getProfileResId(context, profile) + if (profileResId == 0) return null + val resultData = mutableMapOf() + resultData.putAll(realData) + context.resources.getXml(profileResId).use { + var next = it.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (it.name) { + "data" -> { + val key = it.getAttributeValue(null, "key") + val value = it.getAttributeValue(null, "value") + resultData[key] = value + Log.d(TAG, "Overwrite from profile: $key = $value") + } + } + } + next = it.next() + } + } + for (entry in resultData) { + Log.d(TAG, "") + } + return resultData + } catch (e: Exception) { + Log.w(TAG, e) + return null + } + } + + private fun getActiveProfile(context: Context) = getProfileFromSettings(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } + private fun getSerialFromSettings(context: Context): String? = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.SERIAL)) { it.getString(0) } + private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) } + + private fun randomSerial(template: String, prefixLength: Int = (template.length / 2).coerceAtMost(6)): String { + val serial = StringBuilder() + template.forEachIndexed { index, c -> + serial.append(when { + index < prefixLength -> c + c.isDigit() -> '0' + Random.nextInt(10) + c.isLowerCase() && c <= 'f' -> 'a' + Random.nextInt(6) + c.isLowerCase() -> 'a' + Random.nextInt(26) + c.isUpperCase() && c <= 'F' -> 'A' + Random.nextInt(6) + c.isUpperCase() -> 'A' + Random.nextInt(26) + else -> c + }) + } + return serial.toString() + } + + @SuppressLint("MissingPermission") + private fun getProfileSerialTemplate(context: Context, profile: String): String { + // Native + if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) { + return kotlin.runCatching { + if (android.os.Build.VERSION.SDK_INT >= 26) { + android.os.Build.getSerial() + } else { + null + } + }.getOrNull()?.takeIf { it != android.os.Build.UNKNOWN } ?: android.os.Build.SERIAL + } + + // From profile + try { + val profileResId = getProfileResId(context, profile) + if (profileResId != 0) { + context.resources.getXml(profileResId).use { + var next = it.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (it.name) { + "serial" -> return it.getAttributeValue(null, "template") + } + } + next = it.next() + } + } + } + } catch (e: Exception) { + Log.w(TAG, e) + } + + // Fallback + return "008741A0B2C4D6E8" + } + + @SuppressLint("MissingPermission") + private fun getEffectiveProfileSerial(context: Context, profile: String): String { + getSerialFromSettings(context)?.let { return it } + val serialTemplate = getProfileSerialTemplate(context, profile) + val serial = when { + profile == PROFILE_REAL && serialTemplate != android.os.Build.UNKNOWN -> serialTemplate + else -> randomSerial(serialTemplate) + } + saveSerial(context, serial) + return serial + } + + private fun getRealData(): Map = mutableMapOf( + "Build.BOARD" to android.os.Build.BOARD, + "Build.BOOTLOADER" to android.os.Build.BOOTLOADER, + "Build.BRAND" to android.os.Build.BRAND, + "Build.CPU_ABI" to android.os.Build.CPU_ABI, + "Build.CPU_ABI2" to android.os.Build.CPU_ABI2, + "Build.DEVICE" to android.os.Build.DEVICE, + "Build.DISPLAY" to android.os.Build.DISPLAY, + "Build.FINGERPRINT" to android.os.Build.FINGERPRINT, + "Build.HARDWARE" to android.os.Build.HARDWARE, + "Build.HOST" to android.os.Build.HOST, + "Build.ID" to android.os.Build.ID, + "Build.MANUFACTURER" to android.os.Build.MANUFACTURER, + "Build.MODEL" to android.os.Build.MODEL, + "Build.PRODUCT" to android.os.Build.PRODUCT, + "Build.RADIO" to android.os.Build.RADIO, + "Build.SERIAL" to android.os.Build.SERIAL, + "Build.TAGS" to android.os.Build.TAGS, + "Build.TIME" to android.os.Build.TIME.toString(), + "Build.TYPE" to android.os.Build.TYPE, + "Build.USER" to android.os.Build.USER, + "Build.VERSION.CODENAME" to android.os.Build.VERSION.CODENAME, + "Build.VERSION.INCREMENTAL" to android.os.Build.VERSION.INCREMENTAL, + "Build.VERSION.RELEASE" to android.os.Build.VERSION.RELEASE, + "Build.VERSION.SDK" to android.os.Build.VERSION.SDK, + "Build.VERSION.SDK_INT" to android.os.Build.VERSION.SDK_INT.toString() + ).apply { + if (android.os.Build.VERSION.SDK_INT > 21) { + put("Build.SUPPORTED_ABIS", android.os.Build.SUPPORTED_ABIS.joinToString(",")) + } + } + + private fun applyProfileData(profileData: Map) { + fun applyStringField(key: String, valueSetter: (String) -> Unit) = profileData[key]?.let { valueSetter(it) } + fun applyIntField(key: String, valueSetter: (Int) -> Unit) = profileData[key]?.toIntOrNull()?.let { valueSetter(it) } + fun applyLongField(key: String, valueSetter: (Long) -> Unit) = profileData[key]?.toLongOrNull()?.let { valueSetter(it) } + + applyStringField("Build.BOARD") { Build.BOARD = it } + applyStringField("Build.BOOTLOADER") { Build.BOOTLOADER = it } + applyStringField("Build.BRAND") { Build.BRAND = it } + applyStringField("Build.CPU_ABI") { Build.CPU_ABI = it } + applyStringField("Build.CPU_ABI2") { Build.CPU_ABI2 = it } + applyStringField("Build.DEVICE") { Build.DEVICE = it } + applyStringField("Build.DISPLAY") { Build.DISPLAY = it } + applyStringField("Build.FINGERPRINT") { Build.FINGERPRINT = it } + applyStringField("Build.HARDWARE") { Build.HARDWARE = it } + applyStringField("Build.HOST") { Build.HOST = it } + applyStringField("Build.ID") { Build.ID = it } + applyStringField("Build.MANUFACTURER") { Build.MANUFACTURER = it } + applyStringField("Build.MODEL") { Build.MODEL = it } + applyStringField("Build.PRODUCT") { Build.PRODUCT = it } + applyStringField("Build.RADIO") { Build.RADIO = it } + applyStringField("Build.SERIAL") { Build.SERIAL = it } + applyStringField("Build.TAGS") { Build.TAGS = it } + applyLongField("Build.TIME") { Build.TIME = it } + applyStringField("Build.TYPE") { Build.TYPE = it } + applyStringField("Build.USER") { Build.USER = it } + applyStringField("Build.VERSION.CODENAME") { Build.VERSION.CODENAME = it } + applyStringField("Build.VERSION.INCREMENTAL") { Build.VERSION.INCREMENTAL = it } + applyStringField("Build.VERSION.RELEASE") { Build.VERSION.RELEASE = it } + applyStringField("Build.VERSION.SDK") { Build.VERSION.SDK = it } + applyIntField("Build.VERSION.SDK_INT") { Build.VERSION.SDK_INT = it } + if (android.os.Build.VERSION.SDK_INT > 21) { + Build.SUPPORTED_ABIS = profileData["Build.SUPPORTED_ABIS"]?.split(",")?.toTypedArray() ?: emptyArray() + } + } + + private fun applyProfile(context: Context, profile: String) { + val profileData = getProfileData(context, profile, getRealData()) ?: getRealData() + applyProfileData(profileData) + Build.SERIAL = getEffectiveProfileSerial(context, profile) + Log.d(TAG, "Using Serial ${Build.SERIAL}") + } + + fun setProfile(context: Context, profile: String?) { + SettingsContract.setSettings(context, Profile.getContentUri(context)) { + put(Profile.PROFILE, profile) + put(Profile.SERIAL, null as String?) + } + applyProfile(context, profile ?: PROFILE_AUTO) + } + + @JvmStatic + fun ensureInitialized(context: Context) { + if (initialized) return + try { + val profile = getActiveProfile(context) + applyProfile(context, profile) + initialized = true + } catch (e: Exception) { + Log.w(TAG, e) + } + } + + +} 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 7132c7cf..42b8b7cb 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 @@ -131,6 +131,20 @@ object SettingsContract { ) } + object Profile { + private const val id = "profile" + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + + const val PROFILE = "device_profile" + const val SERIAL = "device_profile_serial" + + val PROJECTION = arrayOf( + PROFILE, + SERIAL + ) + } + private fun withoutCallingIdentity(f: () -> T): T { val identity = Binder.clearCallingIdentity() try { 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 3d1f712f..c4adcc96 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 @@ -20,6 +20,7 @@ import org.microg.gms.settings.SettingsContract.CheckIn import org.microg.gms.settings.SettingsContract.DroidGuard import org.microg.gms.settings.SettingsContract.Exposure import org.microg.gms.settings.SettingsContract.Gcm +import org.microg.gms.settings.SettingsContract.Profile import org.microg.gms.settings.SettingsContract.SafetyNet import org.microg.gms.settings.SettingsContract.getAuthority import java.io.File @@ -65,6 +66,7 @@ class SettingsProvider : ContentProvider() { Exposure.getContentUri(context!!) -> queryExposure(projection ?: Exposure.PROJECTION) SafetyNet.getContentUri(context!!) -> querySafetyNet(projection ?: SafetyNet.PROJECTION) DroidGuard.getContentUri(context!!) -> queryDroidGuard(projection ?: DroidGuard.PROJECTION) + Profile.getContentUri(context!!) -> queryProfile(projection ?: Profile.PROJECTION) else -> null } @@ -83,6 +85,7 @@ class SettingsProvider : ContentProvider() { Exposure.getContentUri(context!!) -> updateExposure(values) SafetyNet.getContentUri(context!!) -> updateSafetyNet(values) DroidGuard.getContentUri(context!!) -> updateDroidGuard(values) + Profile.getContentUri(context!!) -> updateProfile(values) else -> return 0 } return 1 @@ -264,6 +267,27 @@ class SettingsProvider : ContentProvider() { editor.apply() } + private fun queryProfile(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + Profile.PROFILE -> getSettingsString(key, "auto") + Profile.SERIAL -> getSettingsString(key) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + + private fun updateProfile(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + Profile.PROFILE -> editor.putString(key, value as String?) + Profile.SERIAL -> editor.putString(key, value as String?) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + private fun MatrixCursor.addRow( p: Array, valueGetter: (String) -> Any? diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java index b65648cb..d0bb1144 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthRequest.java @@ -19,10 +19,11 @@ package org.microg.gms.auth; import android.content.Context; import org.microg.gms.checkin.LastCheckinInfo; -import org.microg.gms.common.Build; +import org.microg.gms.profile.Build; import org.microg.gms.common.Constants; import org.microg.gms.common.HttpFormClient; import org.microg.gms.common.Utils; +import org.microg.gms.profile.ProfileManager; import java.io.IOException; import java.util.Locale; @@ -91,10 +92,11 @@ public class AuthRequest extends HttpFormClient.Request { userAgent = String.format(USER_AGENT, deviceName, buildVersion); } - public AuthRequest build(Build build) { - sdkVersion = build.version_sdk_int; - deviceName = build.device; - buildVersion = build.id; + public AuthRequest build(Context context) { + ProfileManager.ensureInitialized(context); + sdkVersion = Build.VERSION.SDK_INT; + deviceName = Build.DEVICE; + buildVersion = Build.ID; return this; } @@ -111,7 +113,7 @@ public class AuthRequest extends HttpFormClient.Request { } public AuthRequest fromContext(Context context) { - build(Utils.getBuild(context)); + build(context); locale(Utils.getLocale(context)); androidIdHex = Long.toHexString(LastCheckinInfo.read(context).getAndroidId()); return this; diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java index 9186d1a3..87c566c0 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinClient.java @@ -16,13 +16,15 @@ package org.microg.gms.checkin; +import android.content.Context; import android.util.Log; -import org.microg.gms.common.Build; import org.microg.gms.common.DeviceConfiguration; import org.microg.gms.common.DeviceIdentifier; import org.microg.gms.common.PhoneInfo; import org.microg.gms.common.Utils; +import org.microg.gms.profile.Build; +import org.microg.gms.profile.ProfileManager; import java.io.IOException; import java.io.InputStream; @@ -76,29 +78,30 @@ public class CheckinClient { return response; } - public static CheckinRequest makeRequest(Build build, DeviceConfiguration deviceConfiguration, + public static CheckinRequest makeRequest(Context context, DeviceConfiguration deviceConfiguration, DeviceIdentifier deviceIdent, PhoneInfo phoneInfo, LastCheckinInfo checkinInfo, Locale locale, List accounts) { + ProfileManager.ensureInitialized(context); CheckinRequest.Builder builder = new CheckinRequest.Builder() .accountCookie(new ArrayList<>()) .androidId(checkinInfo.getAndroidId()) .checkin(new CheckinRequest.Checkin.Builder() .build(new CheckinRequest.Checkin.Build.Builder() - .bootloader(build.bootloader) - .brand(build.brand) + .bootloader(Build.BOOTLOADER) + .brand(Build.BRAND) .clientId("android-google") - .device(build.device) - .fingerprint(build.fingerprint) - .hardware(build.hardware) - .manufacturer(build.manufacturer) - .model(build.model) + .device(Build.DEVICE) + .fingerprint(Build.FINGERPRINT) + .hardware(Build.HARDWARE) + .manufacturer(Build.MANUFACTURER) + .model(Build.MODEL) .otaInstalled(false) // TODO? //.packageVersionCode(Constants.MAX_REFERENCE_VERSION) - .product(build.product) - .radio(build.radio) - .sdkVersion(build.version_sdk_int) - .time(build.time / 1000) + .product(Build.PRODUCT) + .radio(Build.RADIO) + .sdkVersion(Build.VERSION.SDK_INT) + .time(Build.TIME / 1000) .build()) .cellOperator(phoneInfo.cellOperator) .event(Collections.singletonList(new CheckinRequest.Checkin.Event.Builder() @@ -137,7 +140,7 @@ public class CheckinClient { .loggingId(new Random().nextLong()) // TODO: static .meid(deviceIdent.meid) .otaCert(Collections.singletonList("71Q6Rn2DDZl1zPDVaaeEHItd")) - .serial(build.serial) + .serial(Build.SERIAL) .timeZone(TimeZone.getDefault().getID()) .userName((String) TODO) .userSerialNumber((Integer) TODO) diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java index 95b81984..d6cfdabb 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinManager.java @@ -56,7 +56,7 @@ public class CheckinManager { accounts.add(new CheckinClient.Account(account.name, token)); } } - CheckinRequest request = CheckinClient.makeRequest(Utils.getBuild(context), + CheckinRequest request = CheckinClient.makeRequest(context, new DeviceConfiguration(context), Utils.getDeviceIdentifier(context), Utils.getPhoneInfo(context), info, Utils.getLocale(context), accounts); return handleResponse(context, CheckinClient.request(request)); diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java index 16220724..e17b86fd 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/PushRegisterManager.java @@ -41,7 +41,7 @@ public class PushRegisterManager { RegisterResponse response = new RegisterResponse(); try { response = new RegisterRequest() - .build(Utils.getBuild(context)) + .build(context) .sender(sender) .info(info) .checkin(LastCheckinInfo.read(context)) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java index 43f04028..bcc2f736 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/RegisterRequest.java @@ -16,12 +16,14 @@ package org.microg.gms.gcm; +import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import org.microg.gms.checkin.LastCheckinInfo; -import org.microg.gms.common.Build; import org.microg.gms.common.HttpFormClient; +import org.microg.gms.profile.Build; +import org.microg.gms.profile.ProfileManager; import java.io.IOException; import java.util.LinkedHashMap; @@ -103,9 +105,10 @@ public class RegisterRequest extends HttpFormClient.Request { return this; } - public RegisterRequest build(Build build) { - deviceName = build.device; - buildVersion = build.id; + public RegisterRequest build(Context context) { + ProfileManager.ensureInitialized(context); + deviceName = Build.DEVICE; + buildVersion = Build.ID; return this; } diff --git a/play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java b/play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java index 805814ba..d5eba6d9 100644 --- a/play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java +++ b/play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java @@ -13,10 +13,11 @@ import android.content.pm.Signature; import android.util.Base64; import android.util.Log; -import org.microg.gms.common.Build; import org.microg.gms.common.Constants; import org.microg.gms.common.PackageUtils; import org.microg.gms.common.Utils; +import org.microg.gms.profile.Build; +import org.microg.gms.profile.ProfileManager; import org.microg.gms.snet.AttestRequest; import org.microg.gms.snet.AttestResponse; import org.microg.gms.snet.FileState; @@ -154,6 +155,7 @@ public class Attestation { } private AttestResponse attest(AttestRequest request, String apiKey) throws IOException { + ProfileManager.ensureInitialized(context); String requestUrl = SafetyNetPrefs.get(context).getServiceUrl() + "?alt=PROTO&key=" + apiKey; HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection(); connection.setRequestMethod("POST"); @@ -163,8 +165,7 @@ public class Attestation { connection.setRequestProperty("Accept-Encoding", "gzip"); connection.setRequestProperty("X-Android-Package", packageName); connection.setRequestProperty("X-Android-Cert", PackageUtils.firstSignatureDigest(context, packageName)); - Build build = Utils.getBuild(context); - connection.setRequestProperty("User-Agent", "SafetyNet/" + Constants.GMS_VERSION_CODE + " (" + build.device + " " + build.id + "); gzip"); + connection.setRequestProperty("User-Agent", "SafetyNet/" + Constants.GMS_VERSION_CODE + " (" + Build.DEVICE + " " + Build.ID + "); gzip"); OutputStream os = connection.getOutputStream(); os.write(request.encode()); diff --git a/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java b/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java index 6a84253c..5cdb087a 100644 --- a/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java +++ b/play-services-core/src/main/java/org/microg/gms/wearable/MessageHandler.java @@ -24,7 +24,7 @@ import com.google.android.gms.wearable.ConnectionConfiguration; import com.google.android.gms.wearable.internal.MessageEventParcelable; import org.microg.gms.checkin.LastCheckinInfo; -import org.microg.gms.common.Build; +import org.microg.gms.profile.Build; import org.microg.wearable.ServerMessageListener; import org.microg.wearable.proto.AckAsset; import org.microg.wearable.proto.Connect; @@ -48,7 +48,7 @@ public class MessageHandler extends ServerMessageListener { private String peerNodeId; public MessageHandler(WearableImpl wearable, ConnectionConfiguration config) { - this(wearable, config, new Build().model, config.nodeId, LastCheckinInfo.read(wearable.getContext()).getAndroidId()); + this(wearable, config, Build.MODEL, config.nodeId, LastCheckinInfo.read(wearable.getContext()).getAndroidId()); } private MessageHandler(WearableImpl wearable, ConnectionConfiguration config, String name, String networkId, long androidId) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt index f1ba3435..864c9e83 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/PushRegisterService.kt @@ -148,7 +148,7 @@ class PushRegisterService : LifecycleService() { Log.d(TAG, "register[req]: " + intent.toString() + " extras=" + intent!!.extras) val bundle = completeRegisterRequest(this, database, RegisterRequest() - .build(Utils.getBuild(this)) + .build(this) .sender(intent.getStringExtra(EXTRA_SENDER)) .checkin(LastCheckinInfo.read(this)) .app(packageName) @@ -164,7 +164,7 @@ class PushRegisterService : LifecycleService() { val packageName = intent.appPackageName ?: throw RuntimeException("No package provided") Log.d(TAG, "unregister[req]: " + intent.toString() + " extras=" + intent.extras) val bundle = completeRegisterRequest(this, database, RegisterRequest() - .build(Utils.getBuild(this)) + .build(this) .sender(intent.getStringExtra(EXTRA_SENDER)) .checkin(LastCheckinInfo.read(this)) .app(packageName) @@ -314,7 +314,7 @@ internal class PushRegisterHandler(private val context: Context, private val dat if (!delete) ensureAppRegistrationAllowed(context, database, packageName) val bundle = completeRegisterRequest(context, database, RegisterRequest() - .build(Utils.getBuild(context)) + .build(context) .sender(sender) .checkin(LastCheckinInfo.read(context)) .app(packageName) diff --git a/play-services-core/src/main/res/xml/profile_bullhead_27.xml b/play-services-core/src/main/res/xml/profile_bullhead_27.xml new file mode 100644 index 00000000..66d1a94e --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_bullhead_27.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java index 7d10c103..0db16eb6 100644 --- a/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java @@ -40,7 +40,7 @@ public abstract class TracingIntentService extends IntentService { public PackageInfo getPackageInfo(@NonNull String packageName, int flags) { PackageInfo packageInfo = super.getPackageInfo(packageName, flags); if ("com.google.android.gms".equals(packageName)) { - VersionUtil versionUtil = new VersionUtil(TracingIntentService.this, new org.microg.gms.common.Build()); + VersionUtil versionUtil = new VersionUtil(TracingIntentService.this); packageInfo.versionCode = versionUtil.getVersionCode(); packageInfo.versionName = versionUtil.getVersionString(); packageInfo.sharedUserId = "com.google.uid.shared"; diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt index a8fe931b..0b4aff1f 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt @@ -14,8 +14,9 @@ import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import dalvik.system.DexClassLoader import okio.ByteString.Companion.decodeHex import okio.ByteString.Companion.of -import org.microg.gms.common.Build import org.microg.gms.droidguard.core.BuildConfig +import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager import java.io.File import java.io.IOException import java.security.MessageDigest @@ -25,10 +26,9 @@ import com.android.volley.Request as VolleyRequest import com.android.volley.Response as VolleyResponse class HandleProxyFactory(private val context: Context) { - private val build: Build = Build() private val classMap = hashMapOf>() private val dgDb: DgDatabaseHelper = DgDatabaseHelper(context) - private val version = VersionUtil(context, build) + private val version = VersionUtil(context) private val queue = Volley.newRequestQueue(context) fun createHandle(packageName: String, flow: String?, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { @@ -55,40 +55,42 @@ class HandleProxyFactory(private val context: Context) { } private fun readFromDatabase(flow: String?): Triple? { - val id = "$flow/${version.versionString}/${build.fingerprint}" + ProfileManager.ensureInitialized(context) + val id = "$flow/${version.versionString}/${Build.FINGERPRINT}" return dgDb.get(id) } fun createRequest(flow: String?, packageName: String, pingData: PingData? = null, extra: ByteArray? = null): Request { + ProfileManager.ensureInitialized(context) return Request( usage = Usage(flow, packageName), info = listOf( - KeyValuePair("BOARD", build.board), - KeyValuePair("BOOTLOADER", build.bootloader), - KeyValuePair("BRAND", build.brand), - KeyValuePair("CPU_ABI", build.cpu_abi), - KeyValuePair("CPU_ABI2", build.cpu_abi2), - KeyValuePair("SUPPORTED_ABIS", build.supported_abis.joinToString(",")), - KeyValuePair("DEVICE", build.device), - KeyValuePair("DISPLAY", build.display), - KeyValuePair("FINGERPRINT", build.fingerprint), - KeyValuePair("HARDWARE", build.hardware), - KeyValuePair("HOST", build.host), - KeyValuePair("ID", build.id), - KeyValuePair("MANUFACTURER", build.manufacturer), - KeyValuePair("MODEL", build.model), - KeyValuePair("PRODUCT", build.product), - KeyValuePair("RADIO", build.radio), - KeyValuePair("SERIAL", build.serial), - KeyValuePair("TAGS", build.tags), - KeyValuePair("TIME", build.time.toString()), - KeyValuePair("TYPE", build.type), - KeyValuePair("USER", build.user), - KeyValuePair("VERSION.CODENAME", build.version_codename), - KeyValuePair("VERSION.INCREMENTAL", build.version_incremental), - KeyValuePair("VERSION.RELEASE", build.version_release), - KeyValuePair("VERSION.SDK", build.version_sdk), - KeyValuePair("VERSION.SDK_INT", build.version_sdk_int.toString()), + KeyValuePair("BOARD", Build.BOARD), + KeyValuePair("BOOTLOADER", Build.BOOTLOADER), + KeyValuePair("BRAND", Build.BRAND), + KeyValuePair("CPU_ABI", Build.CPU_ABI), + KeyValuePair("CPU_ABI2", Build.CPU_ABI2), + KeyValuePair("SUPPORTED_ABIS", Build.SUPPORTED_ABIS.joinToString(",")), + KeyValuePair("DEVICE", Build.DEVICE), + KeyValuePair("DISPLAY", Build.DISPLAY), + KeyValuePair("FINGERPRINT", Build.FINGERPRINT), + KeyValuePair("HARDWARE", Build.HARDWARE), + KeyValuePair("HOST", Build.HOST), + KeyValuePair("ID", Build.ID), + KeyValuePair("MANUFACTURER", Build.MANUFACTURER), + KeyValuePair("MODEL", Build.MODEL), + KeyValuePair("PRODUCT", Build.PRODUCT), + KeyValuePair("RADIO", Build.RADIO), + KeyValuePair("SERIAL", Build.SERIAL), + KeyValuePair("TAGS", Build.TAGS), + KeyValuePair("TIME", Build.TIME.toString()), + KeyValuePair("TYPE", Build.TYPE), + KeyValuePair("USER", Build.USER), + KeyValuePair("VERSION.CODENAME", Build.VERSION.CODENAME), + KeyValuePair("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL), + KeyValuePair("VERSION.RELEASE", Build.VERSION.RELEASE), + KeyValuePair("VERSION.SDK", Build.VERSION.SDK), + KeyValuePair("VERSION.SDK_INT", Build.VERSION.SDK_INT.toString()), ), versionName = version.versionString, versionCode = BuildConfig.VERSION_CODE, @@ -107,6 +109,7 @@ class HandleProxyFactory(private val context: Context) { } fun fetchFromServer(flow: String?, request: Request): Triple { + ProfileManager.ensureInitialized(context) val future = RequestFuture.newFuture() queue.add(object : VolleyRequest(Method.POST, SERVER_URL, future) { override fun parseNetworkResponse(response: NetworkResponse): VolleyResponse { @@ -146,11 +149,13 @@ class HandleProxyFactory(private val context: Context) { throw IllegalStateException() } } - val id = "$flow/${version.versionString}/${build.fingerprint}" + val id = "$flow/${version.versionString}/${Build.FINGERPRINT}" val expiry = (response.expiryTimeSecs ?: 0).toLong() - val byteCode = response.byteCode!!.toByteArray() - val extra = response.extra!!.toByteArray() - dgDb.put(id, expiry, vmKey, byteCode, extra) + val byteCode = response.byteCode?.toByteArray() ?: ByteArray(0) + val extra = response.extra?.toByteArray() ?: ByteArray(0) + if (response.save != false) { + dgDb.put(id, expiry, vmKey, byteCode, extra) + } return Triple(vmKey, byteCode, extra) } diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt index c9a4b35f..ce128f4d 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt @@ -6,14 +6,16 @@ package org.microg.gms.droidguard import android.content.Context -import org.microg.gms.common.Build import org.microg.gms.droidguard.core.BuildConfig +import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager -class VersionUtil(private val context: Context, private val build: Build = Build()) { +class VersionUtil(private val context: Context) { val buildType: String get() { + ProfileManager.ensureInitialized(context) // Note: Android TV and Watch use different version codes - val versionCode = when (build.version_sdk_int) { + val versionCode = when (Build.VERSION.SDK_INT) { 31 -> "19" 30 -> "15" 29 -> "12" @@ -22,14 +24,14 @@ class VersionUtil(private val context: Context, private val build: Build = Build 21, 22 -> "02" else -> "00" } - val architectureCode = when (build.cpu_abi) { + val architectureCode = when (Build.CPU_ABI) { "x86_64" -> "08" "x86" -> "07" "arm64-v8a" -> "04" "arm", "armeabi", "armeabi-v7a" -> "03" else -> "00" } - val dpiCode = when (context.resources.displayMetrics.densityDpi) { + val dpiCode = when (context.resources.displayMetrics.densityDpi) { // TODO: Also something to get from profile 160 -> "02" 240 -> "04" 320 -> "06" From 05d18bb8d26d74f24db6111516ae0de8618afc54 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 8 Dec 2021 11:03:11 +0100 Subject: [PATCH 29/63] Do not use application context when requesting resources Application context is not properly themed, resulting in ugly UI on some devices --- .../org/microg/gms/ui/PushNotificationAllAppsFragment.kt | 2 +- .../org/microg/gms/ui/PushNotificationAppFragment.kt | 6 +++--- .../src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt | 8 ++++---- .../nearby/core/ui/ExposureNotificationsAppFragment.kt | 6 +++--- .../core/ui/ExposureNotificationsPreferencesFragment.kt | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt index 91cac572..3be5ae4f 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAllAppsFragment.kt @@ -52,7 +52,7 @@ class PushNotificationAllAppsFragment : PreferenceFragmentCompat() { } private fun updateContent() { - val context = requireContext().applicationContext + val context = requireContext() lifecycleScope.launchWhenResumed { val apps = withContext(Dispatchers.IO) { val res = database.appList.map { app -> diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt index 28cd3b55..6c654344 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/PushNotificationAppFragment.kt @@ -46,13 +46,13 @@ class PushNotificationAppFragment : Fragment(R.layout.push_notification_fragment override fun onResume() { super.onResume() - val appContext = requireContext().applicationContext + val context = requireContext() lifecycleScope.launchWhenResumed { - val pm = appContext.packageManager + val pm = context.packageManager val applicationInfo = pm.getApplicationInfoIfExists(packageName) binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName binding.appIcon = applicationInfo?.loadIcon(pm) - ?: AppCompatResources.getDrawable(appContext, android.R.mipmap.sym_def_app_icon) + ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) } } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index b05bcb16..cdeb2007 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -51,9 +51,9 @@ class SettingsFragment : ResourceSettingsFragment() { override fun onResume() { super.onResume() - val appContext = requireContext().applicationContext + val context = requireContext() lifecycleScope.launchWhenResumed { - updateDetails(appContext) + updateDetails(context) } } @@ -63,7 +63,7 @@ class SettingsFragment : ResourceSettingsFragment() { val database = GcmDatabase(context) val regCount = database.registrationList.size database.close() - findPreference(PREF_GCM)!!.summary = getString(R.string.service_status_enabled_short) + " - " + resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) + findPreference(PREF_GCM)!!.summary = context.getString(R.string.service_status_enabled_short) + " - " + context.resources.getQuantityString(R.plurals.gcm_registered_apps_counter, regCount, regCount) } else { findPreference(PREF_GCM)!!.setSummary(R.string.service_status_disabled_short) } @@ -72,7 +72,7 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(context).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) val backendCount = UnifiedLocationClient[context].getLocationBackends().size + UnifiedLocationClient[context].getGeocoderBackends().size - findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount) + findPreference(PREF_UNIFIEDNLP)!!.summary = context.resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount) findPreference(PREF_EXPOSURE)?.isVisible = NearbyPreferencesIntegration.isAvailable findPreference(PREF_EXPOSURE)?.icon = NearbyPreferencesIntegration.getIcon(context) diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt index 15e95ad0..c2f1ce70 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsAppFragment.kt @@ -40,13 +40,13 @@ class ExposureNotificationsAppFragment : Fragment(R.layout.exposure_notification override fun onResume() { super.onResume() - val appContext = requireContext().applicationContext + val context = requireContext() lifecycleScope.launchWhenResumed { - val pm = appContext.packageManager + val pm = context.packageManager val applicationInfo = pm.getApplicationInfoIfExists(packageName) binding.appName = applicationInfo?.loadLabel(pm)?.toString() ?: packageName binding.appIcon = applicationInfo?.loadIcon(pm) - ?: AppCompatResources.getDrawable(appContext, android.R.mipmap.sym_def_app_icon) + ?: AppCompatResources.getDrawable(context, android.R.mipmap.sym_def_app_icon) } } } diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt index df0731ff..ec89883c 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt @@ -120,7 +120,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { } private fun updateContent() { - val context = requireContext().applicationContext + val context = requireContext() lifecycleScope.launchWhenResumed { handler.postDelayed(updateContentRunnable, UPDATE_CONTENT_INTERVAL) val (apps, lastHourKeys, currentId) = ExposureDatabase.with(context) { database -> From fba6fbdfd0ad0d9025bb586b329f19210ae60413 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 8 Dec 2021 12:04:10 +0100 Subject: [PATCH 30/63] DotChartView: Format date without year --- .../org/microg/gms/nearby/core/ui/DotChartView.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/DotChartView.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/DotChartView.kt index 0ceb71e0..ca8f752e 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/DotChartView.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/DotChartView.kt @@ -11,7 +11,9 @@ import android.content.Context import android.graphics.* import android.os.Build import android.text.format.DateFormat +import android.text.format.DateUtils import android.util.AttributeSet +import android.util.Log import android.view.MotionEvent import android.view.View import org.microg.gms.nearby.exposurenotification.ExposureScanSummary @@ -40,12 +42,13 @@ class DotChartView : View { val dateFormat = DateFormat.getMediumDateFormat(context) val hourFormat = SimpleDateFormat("H") val lowest = dateFormat.parse(dateFormat.format(date))?.time ?: date.time + fun formatDateForView(date: Date) = DateUtils.formatDateTime(context, date.time, DateUtils.FORMAT_ABBREV_MONTH or DateUtils.FORMAT_NO_YEAR) for (day in 0 until 15) { date.time = now - (14 - day) * 24 * 60 * 60 * 1000L - displayData[day] = dateFormat.format(date) to hashMapOf() + displayData[day] = formatDateForView(date) to hashMapOf() } fun dayByDate(date: Date) : Int? { - val dateString = dateFormat.format(date) + val dateString = formatDateForView(date) return displayData.entries.firstOrNull { it.value.first == dateString }?.key } if (value != null) { @@ -113,8 +116,8 @@ class DotChartView : View { maxTextHeight = max(maxTextHeight, fontTempRect.height()) } - val legendLeft = maxTextWidth + 4 * d - val legendBottom = maxTextHeight + 4 * d + val legendLeft = max(56 * d, maxTextWidth + 8 * d) + val legendBottom = maxTextHeight + 8 * d val subHeight = maxTextHeight + 4 * d + paddingBottom val distHeight = (height - innerPadding * 14 - paddingTop - paddingBottom - legendBottom - subHeight).toDouble() @@ -134,7 +137,7 @@ class DotChartView : View { val (dateString, hours) = displayData[day] ?: "" to emptyMap() val top = day * (perHeight + innerPadding) + paddingTop if (day % 2 == 0) { - canvas.drawText(dateString, (paddingLeft + legendLeft - 4 * d), (top + perHeight / 2.0 + maxTextHeight / 2.0).toFloat(), fontPaint) + canvas.drawText(dateString, (paddingLeft + legendLeft - 8 * d), (top + perHeight / 2.0 + maxTextHeight / 2.0).toFloat(), fontPaint) } focusPoint?.let { if (it.y > top && it.y < top + perHeight) focusDay = day } for (hour in 0 until 24) { @@ -162,7 +165,7 @@ class DotChartView : View { } } } - val legendTop = 15 * (perHeight + innerPadding) + paddingTop + maxTextHeight + 4 * d + val legendTop = 15 * (perHeight + innerPadding) + paddingTop + maxTextHeight + 8 * d fontPaint.textAlign = Paint.Align.CENTER for (hour in 0 until 24) { if (hour % 3 == 0) { From d16d438350ae9a45d2b84be8f293284c05fa7258 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 8 Dec 2021 12:05:31 +0100 Subject: [PATCH 31/63] Location: Ensure we don't keep GPS active indoors when requesting app is already gone --- .../gms/location/GoogleLocationManager.java | 25 ++++++++++++++++++- .../gms/location/LocationRequestHelper.java | 23 ++++++++++++++++- .../gms/location/UnifiedLocationProvider.kt | 11 +++++--- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java index 0ad9e872..3ec7ac1b 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java @@ -22,7 +22,10 @@ import android.content.Context; import android.location.Location; import android.location.LocationManager; import android.os.Binder; +import android.os.Handler; +import android.os.Looper; import android.os.RemoteException; +import android.util.Log; import com.google.android.gms.location.ILocationListener; import com.google.android.gms.location.LocationRequest; @@ -39,17 +42,19 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.location.LocationManager.GPS_PROVIDER; -import static android.location.LocationManager.NETWORK_PROVIDER; import static com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY; import static com.google.android.gms.location.LocationRequest.PRIORITY_NO_POWER; public class GoogleLocationManager implements LocationChangeListener { private static final String TAG = "GmsLocManager"; private static final String MOCK_PROVIDER = "mock"; + private static final long VERIFY_CURRENT_REQUESTS_INTERVAL_MS = 5000; // 5 seconds private static final long SWITCH_ON_FRESHNESS_CLIFF_MS = 30000; // 30 seconds private static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION"; private final Context context; + private final Handler handler; + private final Runnable verifyCurrentRequestsRunnable = this::verifyCurrentRequests; private final RealLocationProvider gpsProvider; private final UnifiedLocationProvider networkProvider; private final MockLocationProvider mockProvider; @@ -69,6 +74,7 @@ public class GoogleLocationManager implements LocationChangeListener { this.networkProvider = null; } mockProvider = new MockLocationProvider(this); + handler = new Handler(Looper.getMainLooper()); } public void invokeOnceReady(Runnable runnable) { @@ -203,6 +209,7 @@ public class GoogleLocationManager implements LocationChangeListener { } catch (RemoteException ignored) { } } + verifyCurrentRequests(); } public void setMockMode(boolean mockMode) { @@ -217,6 +224,22 @@ public class GoogleLocationManager implements LocationChangeListener { mockProvider.setLocation(mockLocation); } + private void verifyCurrentRequests() { + handler.removeCallbacks(verifyCurrentRequestsRunnable); + try { + for (int i = 0; i < currentRequests.size(); i++) { + LocationRequestHelper request = currentRequests.get(i); + if (!request.isActive()) { + removeLocationUpdates(request); + i--; + } + } + } catch (Exception e) { + Log.w(TAG, e); + } + handler.postDelayed(verifyCurrentRequestsRunnable, VERIFY_CURRENT_REQUESTS_INTERVAL_MS); + } + @Override public void onLocationChanged() { for (int i = 0; i < currentRequests.size(); i++) { diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java b/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java index ad67ae31..d1991efb 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java @@ -89,12 +89,33 @@ public class LocationRequestHelper { this.callback = data.callback; } + public boolean isActive() { + if (!hasCoarsePermission()) return false; + if (listener != null) { + try { + return listener.asBinder().isBinderAlive(); + } catch (Exception e) { + return false; + } + } else if (pendingIntent != null) { + return true; + } else if (callback != null) { + try { + return callback.asBinder().isBinderAlive(); + } catch (Exception e) { + return false; + } + } else { + return false; + } + } + /** * @return whether to continue sending reports to this {@link LocationRequestHelper} */ public boolean report(Location location) { if (location == null) return true; - if (!hasCoarsePermission()) return false; + if (!isActive()) return false; if (lastReport != null) { if (location.getTime() - lastReport.getTime() < locationRequest.getFastestInterval()) { return true; diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt index e56e6c9f..fa952dca 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt +++ b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt @@ -82,23 +82,26 @@ class UnifiedLocationProvider(context: Context?, changeListener: LocationChangeL Log.d(TAG, "unified network: no longer requesting location update") client.removeLocationUpdates(listener) connected.set(false) - } else if (!requests.isEmpty()) { + } else if (requests.isNotEmpty()) { var minTime = Long.MAX_VALUE + var maxUpdates = Int.MAX_VALUE val sb = StringBuilder() var opPackageName: String? = null for (request in requests) { if (request.locationRequest.interval < minTime) { opPackageName = request.packageName minTime = request.locationRequest.interval + maxUpdates = request.locationRequest.numUpdates } if (sb.isNotEmpty()) sb.append(", ") sb.append("${request.packageName}:${request.locationRequest.interval}ms") } client.opPackageName = opPackageName - Log.d(TAG, "unified network: requesting location updates with interval ${minTime}ms ($sb)") - if (!connected.get() || connectedMinTime != minTime) { - client.requestLocationUpdates(listener, minTime) + if (minTime <= 0) { + client.forceNextUpdate = true } + Log.d(TAG, "unified network: requesting location updates with interval ${minTime}ms ($sb)") + client.requestLocationUpdates(listener, minTime, maxUpdates) connected.set(true) connectedMinTime = minTime } From 110157dab8097bc6c6589c950483731f4175320d Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 14 Jan 2022 10:46:59 +0100 Subject: [PATCH 32/63] ENF: Handle issue with negative or 0 durations due to system time changes With time synchronisation and can happen that timestamps in database are newer than reported system time, which then can lead to reported exposure with multiple measurements but negative or zero reported duration, resulting in divide-by-zero and other issues in average RSSI calculation. This fixes the issue, by ignoring new measurements of the same RPI when they are seemingly older than a previous measurement. --- .../microg/gms/nearby/exposurenotification/ExposureDatabase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index a4323a2a..3166fb9a 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -184,7 +184,7 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } fun noteAdvertisement(rpi: ByteArray, aem: ByteArray, rssi: Int, timestamp: Long = Date().time) = writableDatabase.run { - val update = compileStatement("UPDATE $TABLE_ADVERTISEMENTS SET rssi = ((rssi * duration) + (? * (? - timestamp - duration))) / (? - timestamp), duration = (? - timestamp) WHERE rpi = ? AND timestamp > ? AND timestamp < ?").run { + val update = compileStatement("UPDATE $TABLE_ADVERTISEMENTS SET rssi = IFNULL(((rssi * duration) + (? * MIN(0, ? - timestamp - duration))) / MAX(duration, ? - timestamp), -100), duration = MAX(duration, ? - timestamp) WHERE rpi = ? AND timestamp > ? AND timestamp < ?").run { bindLong(1, rssi.toLong()) bindLong(2, timestamp) bindLong(3, timestamp) From 68e116388bea3758b394497d7cc9b941c54c81dd Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 16 Jan 2022 23:08:51 +0100 Subject: [PATCH 33/63] ENF: Add proper default for new columns --- .../nearby/exposurenotification/ExposureDatabase.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt index 3166fb9a..a702a74c 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureDatabase.kt @@ -18,10 +18,7 @@ import android.os.Parcel import android.os.Parcelable import android.util.Log import androidx.core.content.FileProvider -import com.google.android.gms.nearby.exposurenotification.CalibrationConfidence -import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping -import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration -import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey +import com.google.android.gms.nearby.exposurenotification.* import kotlinx.coroutines.* import okio.ByteString import org.microg.gms.common.PackageUtils @@ -91,10 +88,10 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit } if (oldVersion in 5 until 11) { Log.d(TAG, "Altering tables for version >= 11") - db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN reportType INTEGER NOT NULL;") - db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL;") - db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN reportType INTEGER NOT NULL;") - db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL;") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN reportType INTEGER NOT NULL DEFAULT ${ReportType.UNKNOWN};") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_SINGLE_TOKEN ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL DEFAULT ${TemporaryExposureKey.DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN};") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN reportType INTEGER NOT NULL DEFAULT ${ReportType.UNKNOWN};") + db.execSQL("ALTER TABLE $TABLE_TEK_CHECK_FILE_MATCH ADD COLUMN daysSinceOnsetOfSymptoms INTEGER NOT NULL DEFAULT ${TemporaryExposureKey.DAYS_SINCE_ONSET_OF_SYMPTOMS_UNKNOWN};") } if (oldVersion in 1 until 5) { Log.d(TAG, "Dropping legacy tables from version < 5") From 2dd6b6b173fb8ba40985a8beb81dba23f9867c94 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 13:50:45 +0100 Subject: [PATCH 34/63] Fixes for profile manager --- .../kotlin/org/microg/gms/profile/Build.kt | 3 + .../org/microg/gms/profile/ProfileManager.kt | 58 +++++++++++-------- .../src/main/res/xml/profile_bullhead_27.xml | 2 +- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt index c3cb8853..c13a4d5a 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/profile/Build.kt @@ -90,5 +90,8 @@ object Build { @JvmField var SDK_INT: Int = 0 + + @JvmField + var SECURITY_PATCH: String? = null } } 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 493f8bd0..89251a9b 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 @@ -6,12 +6,12 @@ package org.microg.gms.profile import android.annotation.SuppressLint -import android.content.ContentValues import android.content.Context import android.util.Log import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract.Profile import org.xmlpull.v1.XmlPullParser +import java.util.* import kotlin.random.Random object ProfileManager { @@ -24,18 +24,18 @@ object ProfileManager { private fun getProfileFromSettings(context: Context) = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } private fun getAutoProfile(context: Context): String { - val profile = "${android.os.Build.DEVICE}_${android.os.Build.VERSION.SDK_INT}" + val profile = "${android.os.Build.PRODUCT}_${android.os.Build.VERSION.SDK_INT}" if (hasProfile(context, profile)) return profile return PROFILE_NATIVE } - private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile", null, null) + private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) private fun hasProfile(context: Context, profile: String): Boolean = getProfileResId(context, profile) != 0 - private fun getProfileData(context: Context, profile: String, realData: Map): Map? { + private fun getProfileData(context: Context, profile: String, realData: Map): Map { try { - if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return null + if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData val profileResId = getProfileResId(context, profile) - if (profileResId == 0) return null + if (profileResId == 0) return realData val resultData = mutableMapOf() resultData.putAll(realData) context.resources.getXml(profileResId).use { @@ -54,13 +54,10 @@ object ProfileManager { next = it.next() } } - for (entry in resultData) { - Log.d(TAG, "") - } return resultData } catch (e: Exception) { Log.w(TAG, e) - return null + return realData } } @@ -88,13 +85,16 @@ object ProfileManager { private fun getProfileSerialTemplate(context: Context, profile: String): String { // Native if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) { - return kotlin.runCatching { + var candidate = try { if (android.os.Build.VERSION.SDK_INT >= 26) { android.os.Build.getSerial() } else { - null + android.os.Build.SERIAL } - }.getOrNull()?.takeIf { it != android.os.Build.UNKNOWN } ?: android.os.Build.SERIAL + } catch (e: Exception) { + android.os.Build.SERIAL + } + if (candidate != android.os.Build.UNKNOWN) return candidate } // From profile @@ -160,9 +160,12 @@ object ProfileManager { "Build.VERSION.SDK" to android.os.Build.VERSION.SDK, "Build.VERSION.SDK_INT" to android.os.Build.VERSION.SDK_INT.toString() ).apply { - if (android.os.Build.VERSION.SDK_INT > 21) { + if (android.os.Build.VERSION.SDK_INT >= 21) { put("Build.SUPPORTED_ABIS", android.os.Build.SUPPORTED_ABIS.joinToString(",")) } + if (android.os.Build.VERSION.SDK_INT >= 23) { + put("Build.VERSION.SECURITY_PATCH", android.os.Build.VERSION.SECURITY_PATCH) + } } private fun applyProfileData(profileData: Map) { @@ -195,8 +198,15 @@ object ProfileManager { applyStringField("Build.VERSION.RELEASE") { Build.VERSION.RELEASE = it } applyStringField("Build.VERSION.SDK") { Build.VERSION.SDK = it } applyIntField("Build.VERSION.SDK_INT") { Build.VERSION.SDK_INT = it } - if (android.os.Build.VERSION.SDK_INT > 21) { + if (android.os.Build.VERSION.SDK_INT >= 21) { Build.SUPPORTED_ABIS = profileData["Build.SUPPORTED_ABIS"]?.split(",")?.toTypedArray() ?: emptyArray() + } else { + Build.SUPPORTED_ABIS = emptyArray() + } + if (android.os.Build.VERSION.SDK_INT >= 23) { + Build.VERSION.SECURITY_PATCH = profileData["Build.VERSION.SECURITY_PATCH"] + } else { + Build.VERSION.SECURITY_PATCH = null } } @@ -217,15 +227,15 @@ object ProfileManager { @JvmStatic fun ensureInitialized(context: Context) { - if (initialized) return - try { - val profile = getActiveProfile(context) - applyProfile(context, profile) - initialized = true - } catch (e: Exception) { - Log.w(TAG, e) + synchronized(this) { + if (initialized) return + try { + val profile = getActiveProfile(context) + applyProfile(context, profile) + initialized = true + } catch (e: Exception) { + Log.w(TAG, e) + } } } - - } diff --git a/play-services-core/src/main/res/xml/profile_bullhead_27.xml b/play-services-core/src/main/res/xml/profile_bullhead_27.xml index 66d1a94e..5621421e 100644 --- a/play-services-core/src/main/res/xml/profile_bullhead_27.xml +++ b/play-services-core/src/main/res/xml/profile_bullhead_27.xml @@ -3,7 +3,7 @@ ~ SPDX-FileCopyrightText: 2021, microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> - + From ee91cc9b79ed6de453ee077d6fcc9ff01bf289a5 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 13:51:16 +0100 Subject: [PATCH 35/63] Update Gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 303 ++++++++++++++--------- gradlew.bat | 90 +++++++ 4 files changed, 278 insertions(+), 117 deletions(-) create mode 100644 gradlew.bat diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch literal 59536 zcma&NbC71ylI~qywr$(CZQJHswz}-9F59+k+g;UV+cs{`J?GrGXYR~=-ydruB3JCa zB64N^cILAcWk5iofq)<(fq;O7{th4@;QxID0)qN`mJ?GIqLY#rX8-|G{5M0pdVW5^ zzXk$-2kQTAC?_N@B`&6-N-rmVFE=$QD?>*=4<|!MJu@}isLc4AW#{m2if&A5T5g&~ ziuMQeS*U5sL6J698wOd)K@oK@1{peP5&Esut<#VH^u)gp`9H4)`uE!2$>RTctN+^u z=ASkePDZA-X8)rp%D;p*~P?*a_=*Kwc<^>QSH|^<0>o37lt^+Mj1;4YvJ(JR-Y+?%Nu}JAYj5 z_Qc5%Ao#F?q32i?ZaN2OSNhWL;2oDEw_({7ZbgUjna!Fqn3NzLM@-EWFPZVmc>(fZ z0&bF-Ch#p9C{YJT9Rcr3+Y_uR^At1^BxZ#eo>$PLJF3=;t_$2|t+_6gg5(j{TmjYU zK12c&lE?Eh+2u2&6Gf*IdKS&6?rYbSEKBN!rv{YCm|Rt=UlPcW9j`0o6{66#y5t9C zruFA2iKd=H%jHf%ypOkxLnO8#H}#Zt{8p!oi6)7#NqoF({t6|J^?1e*oxqng9Q2Cc zg%5Vu!em)}Yuj?kaP!D?b?(C*w!1;>R=j90+RTkyEXz+9CufZ$C^umX^+4|JYaO<5 zmIM3#dv`DGM;@F6;(t!WngZSYzHx?9&$xEF70D1BvfVj<%+b#)vz)2iLCrTeYzUcL z(OBnNoG6Le%M+@2oo)&jdOg=iCszzv59e zDRCeaX8l1hC=8LbBt|k5?CXgep=3r9BXx1uR8!p%Z|0+4Xro=xi0G!e{c4U~1j6!) zH6adq0}#l{%*1U(Cb%4AJ}VLWKBPi0MoKFaQH6x?^hQ!6em@993xdtS%_dmevzeNl z(o?YlOI=jl(`L9^ z0O+H9k$_@`6L13eTT8ci-V0ljDMD|0ifUw|Q-Hep$xYj0hTO@0%IS^TD4b4n6EKDG z??uM;MEx`s98KYN(K0>c!C3HZdZ{+_53DO%9k5W%pr6yJusQAv_;IA}925Y%;+!tY z%2k!YQmLLOr{rF~!s<3-WEUs)`ix_mSU|cNRBIWxOox_Yb7Z=~Q45ZNe*u|m^|)d* zog=i>`=bTe!|;8F+#H>EjIMcgWcG2ORD`w0WD;YZAy5#s{65~qfI6o$+Ty&-hyMyJ z3Ra~t>R!p=5ZpxA;QkDAoPi4sYOP6>LT+}{xp}tk+<0k^CKCFdNYG(Es>p0gqD)jP zWOeX5G;9(m@?GOG7g;e74i_|SmE?`B2i;sLYwRWKLy0RLW!Hx`=!LH3&k=FuCsM=9M4|GqzA)anEHfxkB z?2iK-u(DC_T1};KaUT@3nP~LEcENT^UgPvp!QC@Dw&PVAhaEYrPey{nkcn(ro|r7XUz z%#(=$7D8uP_uU-oPHhd>>^adbCSQetgSG`e$U|7mr!`|bU0aHl_cmL)na-5x1#OsVE#m*+k84Y^+UMeSAa zbrVZHU=mFwXEaGHtXQq`2ZtjfS!B2H{5A<3(nb-6ARVV8kEmOkx6D2x7~-6hl;*-*}2Xz;J#a8Wn;_B5=m zl3dY;%krf?i-Ok^Pal-}4F`{F@TYPTwTEhxpZK5WCpfD^UmM_iYPe}wpE!Djai6_{ z*pGO=WB47#Xjb7!n2Ma)s^yeR*1rTxp`Mt4sfA+`HwZf%!7ZqGosPkw69`Ix5Ku6G z@Pa;pjzV&dn{M=QDx89t?p?d9gna*}jBly*#1!6}5K<*xDPJ{wv4& zM$17DFd~L*Te3A%yD;Dp9UGWTjRxAvMu!j^Tbc}2v~q^59d4bz zvu#!IJCy(BcWTc`;v$9tH;J%oiSJ_i7s;2`JXZF+qd4C)vY!hyCtl)sJIC{ebI*0> z@x>;EzyBv>AI-~{D6l6{ST=em*U( z(r$nuXY-#CCi^8Z2#v#UXOt`dbYN1z5jzNF2 z411?w)whZrfA20;nl&C1Gi+gk<`JSm+{|*2o<< zqM#@z_D`Cn|0H^9$|Tah)0M_X4c37|KQ*PmoT@%xHc3L1ZY6(p(sNXHa&49Frzto& zR`c~ClHpE~4Z=uKa5S(-?M8EJ$zt0&fJk~p$M#fGN1-y$7!37hld`Uw>Urri(DxLa;=#rK0g4J)pXMC zxzraOVw1+kNWpi#P=6(qxf`zSdUC?D$i`8ZI@F>k6k zz21?d+dw7b&i*>Kv5L(LH-?J%@WnqT7j#qZ9B>|Zl+=> z^U-pV@1y_ptHo4hl^cPRWewbLQ#g6XYQ@EkiP z;(=SU!yhjHp%1&MsU`FV1Z_#K1&(|5n(7IHbx&gG28HNT)*~-BQi372@|->2Aw5It z0CBpUcMA*QvsPy)#lr!lIdCi@1k4V2m!NH)%Px(vu-r(Q)HYc!p zJ^$|)j^E#q#QOgcb^pd74^JUi7fUmMiNP_o*lvx*q%_odv49Dsv$NV;6J z9GOXKomA{2Pb{w}&+yHtH?IkJJu~}Z?{Uk++2mB8zyvh*xhHKE``99>y#TdD z&(MH^^JHf;g(Tbb^&8P*;_i*2&fS$7${3WJtV7K&&(MBV2~)2KB3%cWg#1!VE~k#C z!;A;?p$s{ihyojEZz+$I1)L}&G~ml=udD9qh>Tu(ylv)?YcJT3ihapi!zgPtWb*CP zlLLJSRCj-^w?@;RU9aL2zDZY1`I3d<&OMuW=c3$o0#STpv_p3b9Wtbql>w^bBi~u4 z3D8KyF?YE?=HcKk!xcp@Cigvzy=lnFgc^9c%(^F22BWYNAYRSho@~*~S)4%AhEttv zvq>7X!!EWKG?mOd9&n>vvH1p4VzE?HCuxT-u+F&mnsfDI^}*-d00-KAauEaXqg3k@ zy#)MGX!X;&3&0s}F3q40ZmVM$(H3CLfpdL?hB6nVqMxX)q=1b}o_PG%r~hZ4gUfSp zOH4qlEOW4OMUc)_m)fMR_rl^pCfXc{$fQbI*E&mV77}kRF z&{<06AJyJ!e863o-V>FA1a9Eemx6>^F$~9ppt()ZbPGfg_NdRXBWoZnDy2;#ODgf! zgl?iOcF7Meo|{AF>KDwTgYrJLb$L2%%BEtO>T$C?|9bAB&}s;gI?lY#^tttY&hfr# zKhC+&b-rpg_?~uVK%S@mQleU#_xCsvIPK*<`E0fHE1&!J7!xD#IB|SSPW6-PyuqGn3^M^Rz%WT{e?OI^svARX&SAdU77V(C~ zM$H{Kg59op{<|8ry9ecfP%=kFm(-!W&?U0@<%z*+!*<e0XesMxRFu9QnGqun6R_%T+B%&9Dtk?*d$Q zb~>84jEAPi@&F@3wAa^Lzc(AJz5gsfZ7J53;@D<;Klpl?sK&u@gie`~vTsbOE~Cd4 z%kr56mI|#b(Jk&;p6plVwmNB0H@0SmgdmjIn5Ne@)}7Vty(yb2t3ev@22AE^s!KaN zyQ>j+F3w=wnx7w@FVCRe+`vUH)3gW%_72fxzqX!S&!dchdkRiHbXW1FMrIIBwjsai8`CB2r4mAbwp%rrO>3B$Zw;9=%fXI9B{d(UzVap7u z6piC-FQ)>}VOEuPpuqznpY`hN4dGa_1Xz9rVg(;H$5Te^F0dDv*gz9JS<|>>U0J^# z6)(4ICh+N_Q`Ft0hF|3fSHs*?a=XC;e`sJaU9&d>X4l?1W=|fr!5ShD|nv$GK;j46@BV6+{oRbWfqOBRb!ir88XD*SbC(LF}I1h#6@dvK%Toe%@ zhDyG$93H8Eu&gCYddP58iF3oQH*zLbNI;rN@E{T9%A8!=v#JLxKyUe}e}BJpB{~uN zqgxRgo0*-@-iaHPV8bTOH(rS(huwK1Xg0u+e!`(Irzu@Bld&s5&bWgVc@m7;JgELd zimVs`>vQ}B_1(2#rv#N9O`fJpVfPc7V2nv34PC);Dzbb;p!6pqHzvy?2pD&1NE)?A zt(t-ucqy@wn9`^MN5apa7K|L=9>ISC>xoc#>{@e}m#YAAa1*8-RUMKwbm|;5p>T`Z zNf*ph@tnF{gmDa3uwwN(g=`Rh)4!&)^oOy@VJaK4lMT&5#YbXkl`q?<*XtsqD z9PRK6bqb)fJw0g-^a@nu`^?71k|m3RPRjt;pIkCo1{*pdqbVs-Yl>4E>3fZx3Sv44grW=*qdSoiZ9?X0wWyO4`yDHh2E!9I!ZFi zVL8|VtW38}BOJHW(Ax#KL_KQzarbuE{(%TA)AY)@tY4%A%P%SqIU~8~-Lp3qY;U-} z`h_Gel7;K1h}7$_5ZZT0&%$Lxxr-<89V&&TCsu}LL#!xpQ1O31jaa{U34~^le*Y%L za?7$>Jk^k^pS^_M&cDs}NgXlR>16AHkSK-4TRaJSh#h&p!-!vQY%f+bmn6x`4fwTp z$727L^y`~!exvmE^W&#@uY!NxJi`g!i#(++!)?iJ(1)2Wk;RN zFK&O4eTkP$Xn~4bB|q8y(btx$R#D`O@epi4ofcETrx!IM(kWNEe42Qh(8*KqfP(c0 zouBl6>Fc_zM+V;F3znbo{x#%!?mH3`_ANJ?y7ppxS@glg#S9^MXu|FM&ynpz3o&Qh z2ujAHLF3($pH}0jXQsa#?t--TnF1P73b?4`KeJ9^qK-USHE)4!IYgMn-7z|=ALF5SNGkrtPG@Y~niUQV2?g$vzJN3nZ{7;HZHzWAeQ;5P|@Tl3YHpyznGG4-f4=XflwSJY+58-+wf?~Fg@1p1wkzuu-RF3j2JX37SQUc? zQ4v%`V8z9ZVZVqS8h|@@RpD?n0W<=hk=3Cf8R?d^9YK&e9ZybFY%jdnA)PeHvtBe- zhMLD+SSteHBq*q)d6x{)s1UrsO!byyLS$58WK;sqip$Mk{l)Y(_6hEIBsIjCr5t>( z7CdKUrJTrW%qZ#1z^n*Lb8#VdfzPw~OIL76aC+Rhr<~;4Tl!sw?Rj6hXj4XWa#6Tp z@)kJ~qOV)^Rh*-?aG>ic2*NlC2M7&LUzc9RT6WM%Cpe78`iAowe!>(T0jo&ivn8-7 zs{Qa@cGy$rE-3AY0V(l8wjI^uB8Lchj@?L}fYal^>T9z;8juH@?rG&g-t+R2dVDBe zq!K%{e-rT5jX19`(bP23LUN4+_zh2KD~EAYzhpEO3MUG8@}uBHH@4J zd`>_(K4q&>*k82(dDuC)X6JuPrBBubOg7qZ{?x!r@{%0);*`h*^F|%o?&1wX?Wr4b z1~&cy#PUuES{C#xJ84!z<1tp9sfrR(i%Tu^jnXy;4`Xk;AQCdFC@?V%|; zySdC7qS|uQRcH}EFZH%mMB~7gi}a0utE}ZE_}8PQH8f;H%PN41Cb9R%w5Oi5el^fd z$n{3SqLCnrF##x?4sa^r!O$7NX!}&}V;0ZGQ&K&i%6$3C_dR%I7%gdQ;KT6YZiQrW zk%q<74oVBV>@}CvJ4Wj!d^?#Zwq(b$E1ze4$99DuNg?6t9H}k_|D7KWD7i0-g*EO7 z;5{hSIYE4DMOK3H%|f5Edx+S0VI0Yw!tsaRS2&Il2)ea^8R5TG72BrJue|f_{2UHa z@w;^c|K3da#$TB0P3;MPlF7RuQeXT$ zS<<|C0OF(k)>fr&wOB=gP8!Qm>F41u;3esv7_0l%QHt(~+n; zf!G6%hp;Gfa9L9=AceiZs~tK+Tf*Wof=4!u{nIO90jH@iS0l+#%8=~%ASzFv7zqSB^?!@N7)kp0t&tCGLmzXSRMRyxCmCYUD2!B`? zhs$4%KO~m=VFk3Buv9osha{v+mAEq=ik3RdK@;WWTV_g&-$U4IM{1IhGX{pAu%Z&H zFfwCpUsX%RKg);B@7OUzZ{Hn{q6Vv!3#8fAg!P$IEx<0vAx;GU%}0{VIsmFBPq_mb zpe^BChDK>sc-WLKl<6 zwbW|e&d&dv9Wu0goueyu>(JyPx1mz0v4E?cJjFuKF71Q1)AL8jHO$!fYT3(;U3Re* zPPOe%*O+@JYt1bW`!W_1!mN&=w3G9ru1XsmwfS~BJ))PhD(+_J_^N6j)sx5VwbWK| zwRyC?W<`pOCY)b#AS?rluxuuGf-AJ=D!M36l{ua?@SJ5>e!IBr3CXIxWw5xUZ@Xrw z_R@%?{>d%Ld4p}nEsiA@v*nc6Ah!MUs?GA7e5Q5lPpp0@`%5xY$C;{%rz24$;vR#* zBP=a{)K#CwIY%p} zXVdxTQ^HS@O&~eIftU+Qt^~(DGxrdi3k}DdT^I7Iy5SMOp$QuD8s;+93YQ!OY{eB24%xY7ml@|M7I(Nb@K_-?F;2?et|CKkuZK_>+>Lvg!>JE~wN`BI|_h6$qi!P)+K-1Hh(1;a`os z55)4Q{oJiA(lQM#;w#Ta%T0jDNXIPM_bgESMCDEg6rM33anEr}=|Fn6)|jBP6Y}u{ zv9@%7*#RI9;fv;Yii5CI+KrRdr0DKh=L>)eO4q$1zmcSmglsV`*N(x=&Wx`*v!!hn6X-l0 zP_m;X??O(skcj+oS$cIdKhfT%ABAzz3w^la-Ucw?yBPEC+=Pe_vU8nd-HV5YX6X8r zZih&j^eLU=%*;VzhUyoLF;#8QsEfmByk+Y~caBqSvQaaWf2a{JKB9B>V&r?l^rXaC z8)6AdR@Qy_BxQrE2Fk?ewD!SwLuMj@&d_n5RZFf7=>O>hzVE*seW3U?_p|R^CfoY`?|#x9)-*yjv#lo&zP=uI`M?J zbzC<^3x7GfXA4{FZ72{PE*-mNHyy59Q;kYG@BB~NhTd6pm2Oj=_ zizmD?MKVRkT^KmXuhsk?eRQllPo2Ubk=uCKiZ&u3Xjj~<(!M94c)Tez@9M1Gfs5JV z->@II)CDJOXTtPrQudNjE}Eltbjq>6KiwAwqvAKd^|g!exgLG3;wP+#mZYr`cy3#39e653d=jrR-ulW|h#ddHu(m9mFoW~2yE zz5?dB%6vF}+`-&-W8vy^OCxm3_{02royjvmwjlp+eQDzFVEUiyO#gLv%QdDSI#3W* z?3!lL8clTaNo-DVJw@ynq?q!%6hTQi35&^>P85G$TqNt78%9_sSJt2RThO|JzM$iL zg|wjxdMC2|Icc5rX*qPL(coL!u>-xxz-rFiC!6hD1IR%|HSRsV3>Kq~&vJ=s3M5y8SG%YBQ|{^l#LGlg!D?E>2yR*eV%9m$_J6VGQ~AIh&P$_aFbh zULr0Z$QE!QpkP=aAeR4ny<#3Fwyw@rZf4?Ewq`;mCVv}xaz+3ni+}a=k~P+yaWt^L z@w67!DqVf7D%7XtXX5xBW;Co|HvQ8WR1k?r2cZD%U;2$bsM%u8{JUJ5Z0k= zZJARv^vFkmWx15CB=rb=D4${+#DVqy5$C%bf`!T0+epLJLnh1jwCdb*zuCL}eEFvE z{rO1%gxg>1!W(I!owu*mJZ0@6FM(?C+d*CeceZRW_4id*D9p5nzMY&{mWqrJomjIZ z97ZNnZ3_%Hx8dn;H>p8m7F#^2;T%yZ3H;a&N7tm=Lvs&lgJLW{V1@h&6Vy~!+Ffbb zv(n3+v)_D$}dqd!2>Y2B)#<+o}LH#%ogGi2-?xRIH)1!SD)u-L65B&bsJTC=LiaF+YOCif2dUX6uAA|#+vNR z>U+KQekVGon)Yi<93(d!(yw1h3&X0N(PxN2{%vn}cnV?rYw z$N^}_o!XUB!mckL`yO1rnUaI4wrOeQ(+&k?2mi47hzxSD`N#-byqd1IhEoh!PGq>t z_MRy{5B0eKY>;Ao3z$RUU7U+i?iX^&r739F)itdrTpAi-NN0=?^m%?{A9Ly2pVv>Lqs6moTP?T2-AHqFD-o_ znVr|7OAS#AEH}h8SRPQ@NGG47dO}l=t07__+iK8nHw^(AHx&Wb<%jPc$$jl6_p(b$ z)!pi(0fQodCHfM)KMEMUR&UID>}m^(!{C^U7sBDOA)$VThRCI0_+2=( zV8mMq0R(#z;C|7$m>$>`tX+T|xGt(+Y48@ZYu#z;0pCgYgmMVbFb!$?%yhZqP_nhn zy4<#3P1oQ#2b51NU1mGnHP$cf0j-YOgAA}A$QoL6JVLcmExs(kU{4z;PBHJD%_=0F z>+sQV`mzijSIT7xn%PiDKHOujX;n|M&qr1T@rOxTdxtZ!&u&3HHFLYD5$RLQ=heur zb>+AFokUVQeJy-#LP*^)spt{mb@Mqe=A~-4p0b+Bt|pZ+@CY+%x}9f}izU5;4&QFE zO1bhg&A4uC1)Zb67kuowWY4xbo&J=%yoXlFB)&$d*-}kjBu|w!^zbD1YPc0-#XTJr z)pm2RDy%J3jlqSMq|o%xGS$bPwn4AqitC6&e?pqWcjWPt{3I{>CBy;hg0Umh#c;hU3RhCUX=8aR>rmd` z7Orw(5tcM{|-^J?ZAA9KP|)X6n9$-kvr#j5YDecTM6n z&07(nD^qb8hpF0B^z^pQ*%5ePYkv&FabrlI61ntiVp!!C8y^}|<2xgAd#FY=8b*y( zuQOuvy2`Ii^`VBNJB&R!0{hABYX55ooCAJSSevl4RPqEGb)iy_0H}v@vFwFzD%>#I>)3PsouQ+_Kkbqy*kKdHdfkN7NBcq%V{x^fSxgXpg7$bF& zj!6AQbDY(1u#1_A#1UO9AxiZaCVN2F0wGXdY*g@x$ByvUA?ePdide0dmr#}udE%K| z3*k}Vv2Ew2u1FXBaVA6aerI36R&rzEZeDDCl5!t0J=ug6kuNZzH>3i_VN`%BsaVB3 zQYw|Xub_SGf{)F{$ZX5`Jc!X!;eybjP+o$I{Z^Hsj@D=E{MnnL+TbC@HEU2DjG{3-LDGIbq()U87x4eS;JXnSh;lRlJ z>EL3D>wHt-+wTjQF$fGyDO$>d+(fq@bPpLBS~xA~R=3JPbS{tzN(u~m#Po!?H;IYv zE;?8%^vle|%#oux(Lj!YzBKv+Fd}*Ur-dCBoX*t{KeNM*n~ZPYJ4NNKkI^MFbz9!v z4(Bvm*Kc!-$%VFEewYJKz-CQN{`2}KX4*CeJEs+Q(!kI%hN1!1P6iOq?ovz}X0IOi z)YfWpwW@pK08^69#wSyCZkX9?uZD?C^@rw^Y?gLS_xmFKkooyx$*^5#cPqntNTtSG zlP>XLMj2!VF^0k#ole7`-c~*~+_T5ls?x4)ah(j8vo_ zwb%S8qoaZqY0-$ZI+ViIA_1~~rAH7K_+yFS{0rT@eQtTAdz#8E5VpwnW!zJ_^{Utv zlW5Iar3V5t&H4D6A=>?mq;G92;1cg9a2sf;gY9pJDVKn$DYdQlvfXq}zz8#LyPGq@ z+`YUMD;^-6w&r-82JL7mA8&M~Pj@aK!m{0+^v<|t%APYf7`}jGEhdYLqsHW-Le9TL z_hZZ1gbrz7$f9^fAzVIP30^KIz!!#+DRLL+qMszvI_BpOSmjtl$hh;&UeM{ER@INV zcI}VbiVTPoN|iSna@=7XkP&-4#06C};8ajbxJ4Gcq8(vWv4*&X8bM^T$mBk75Q92j z1v&%a;OSKc8EIrodmIiw$lOES2hzGDcjjB`kEDfJe{r}yE6`eZL zEB`9u>Cl0IsQ+t}`-cx}{6jqcANucqIB>Qmga_&<+80E2Q|VHHQ$YlAt{6`Qu`HA3 z03s0-sSlwbvgi&_R8s={6<~M^pGvBNjKOa>tWenzS8s zR>L7R5aZ=mSU{f?ib4Grx$AeFvtO5N|D>9#)ChH#Fny2maHWHOf2G=#<9Myot#+4u zWVa6d^Vseq_0=#AYS(-m$Lp;*8nC_6jXIjEM`omUmtH@QDs3|G)i4j*#_?#UYVZvJ z?YjT-?!4Q{BNun;dKBWLEw2C-VeAz`%?A>p;)PL}TAZn5j~HK>v1W&anteARlE+~+ zj>c(F;?qO3pXBb|#OZdQnm<4xWmn~;DR5SDMxt0UK_F^&eD|KZ=O;tO3vy4@4h^;2 zUL~-z`-P1aOe?|ZC1BgVsL)2^J-&vIFI%q@40w0{jjEfeVl)i9(~bt2z#2Vm)p`V_ z1;6$Ae7=YXk#=Qkd24Y23t&GvRxaOoad~NbJ+6pxqzJ>FY#Td7@`N5xp!n(c!=RE& z&<<@^a$_Ys8jqz4|5Nk#FY$~|FPC0`*a5HH!|Gssa9=~66&xG9)|=pOOJ2KE5|YrR zw!w6K2aC=J$t?L-;}5hn6mHd%hC;p8P|Dgh6D>hGnXPgi;6r+eA=?f72y9(Cf_ho{ zH6#)uD&R=73^$$NE;5piWX2bzR67fQ)`b=85o0eOLGI4c-Tb@-KNi2pz=Ke@SDcPn za$AxXib84`!Sf;Z3B@TSo`Dz7GM5Kf(@PR>Ghzi=BBxK8wRp>YQoXm+iL>H*Jo9M3 z6w&E?BC8AFTFT&Tv8zf+m9<&S&%dIaZ)Aoqkak_$r-2{$d~0g2oLETx9Y`eOAf14QXEQw3tJne;fdzl@wV#TFXSLXM2428F-Q}t+n2g%vPRMUzYPvzQ9f# zu(liiJem9P*?0%V@RwA7F53r~|I!Ty)<*AsMX3J{_4&}{6pT%Tpw>)^|DJ)>gpS~1rNEh z0$D?uO8mG?H;2BwM5a*26^7YO$XjUm40XmBsb63MoR;bJh63J;OngS5sSI+o2HA;W zdZV#8pDpC9Oez&L8loZO)MClRz!_!WD&QRtQxnazhT%Vj6Wl4G11nUk8*vSeVab@N#oJ}`KyJv+8Mo@T1-pqZ1t|?cnaVOd;1(h9 z!$DrN=jcGsVYE-0-n?oCJ^4x)F}E;UaD-LZUIzcD?W^ficqJWM%QLy6QikrM1aKZC zi{?;oKwq^Vsr|&`i{jIphA8S6G4)$KGvpULjH%9u(Dq247;R#l&I0{IhcC|oBF*Al zvLo7Xte=C{aIt*otJD}BUq)|_pdR>{zBMT< z(^1RpZv*l*m*OV^8>9&asGBo8h*_4q*)-eCv*|Pq=XNGrZE)^(SF7^{QE_~4VDB(o zVcPA_!G+2CAtLbl+`=Q~9iW`4ZRLku!uB?;tWqVjB0lEOf}2RD7dJ=BExy=<9wkb- z9&7{XFA%n#JsHYN8t5d~=T~5DcW4$B%3M+nNvC2`0!#@sckqlzo5;hhGi(D9=*A4` z5ynobawSPRtWn&CDLEs3Xf`(8^zDP=NdF~F^s&={l7(aw&EG}KWpMjtmz7j_VLO;@ zM2NVLDxZ@GIv7*gzl1 zjq78tv*8#WSY`}Su0&C;2F$Ze(q>F(@Wm^Gw!)(j;dk9Ad{STaxn)IV9FZhm*n+U} zi;4y*3v%A`_c7a__DJ8D1b@dl0Std3F||4Wtvi)fCcBRh!X9$1x!_VzUh>*S5s!oq z;qd{J_r79EL2wIeiGAqFstWtkfIJpjVh%zFo*=55B9Zq~y0=^iqHWfQl@O!Ak;(o*m!pZqe9 z%U2oDOhR)BvW8&F70L;2TpkzIutIvNQaTjjs5V#8mV4!NQ}zN=i`i@WI1z0eN-iCS z;vL-Wxc^Vc_qK<5RPh(}*8dLT{~GzE{w2o$2kMFaEl&q zP{V=>&3kW7tWaK-Exy{~`v4J0U#OZBk{a9{&)&QG18L@6=bsZ1zC_d{{pKZ-Ey>I> z;8H0t4bwyQqgu4hmO`3|4K{R*5>qnQ&gOfdy?z`XD%e5+pTDzUt3`k^u~SaL&XMe= z9*h#kT(*Q9jO#w2Hd|Mr-%DV8i_1{J1MU~XJ3!WUplhXDYBpJH><0OU`**nIvPIof z|N8@I=wA)sf45SAvx||f?Z5uB$kz1qL3Ky_{%RPdP5iN-D2!p5scq}buuC00C@jom zhfGKm3|f?Z0iQ|K$Z~!`8{nmAS1r+fp6r#YDOS8V*;K&Gs7Lc&f^$RC66O|)28oh`NHy&vq zJh+hAw8+ybTB0@VhWN^0iiTnLsCWbS_y`^gs!LX!Lw{yE``!UVzrV24tP8o;I6-65 z1MUiHw^{bB15tmrVT*7-#sj6cs~z`wk52YQJ*TG{SE;KTm#Hf#a~|<(|ImHH17nNM z`Ub{+J3dMD!)mzC8b(2tZtokKW5pAwHa?NFiso~# z1*iaNh4lQ4TS)|@G)H4dZV@l*Vd;Rw;-;odDhW2&lJ%m@jz+Panv7LQm~2Js6rOW3 z0_&2cW^b^MYW3)@o;neZ<{B4c#m48dAl$GCc=$>ErDe|?y@z`$uq3xd(%aAsX)D%l z>y*SQ%My`yDP*zof|3@_w#cjaW_YW4BdA;#Glg1RQcJGY*CJ9`H{@|D+*e~*457kd z73p<%fB^PV!Ybw@)Dr%(ZJbX}xmCStCYv#K3O32ej{$9IzM^I{6FJ8!(=azt7RWf4 z7ib0UOPqN40X!wOnFOoddd8`!_IN~9O)#HRTyjfc#&MCZ zZAMzOVB=;qwt8gV?{Y2?b=iSZG~RF~uyx18K)IDFLl})G1v@$(s{O4@RJ%OTJyF+Cpcx4jmy|F3euCnMK!P2WTDu5j z{{gD$=M*pH!GGzL%P)V2*ROm>!$Y=z|D`!_yY6e7SU$~a5q8?hZGgaYqaiLnkK%?0 zs#oI%;zOxF@g*@(V4p!$7dS1rOr6GVs6uYCTt2h)eB4?(&w8{#o)s#%gN@BBosRUe z)@P@8_Zm89pr~)b>e{tbPC~&_MR--iB{=)y;INU5#)@Gix-YpgP<-c2Ms{9zuCX|3 z!p(?VaXww&(w&uBHzoT%!A2=3HAP>SDxcljrego7rY|%hxy3XlODWffO_%g|l+7Y_ zqV(xbu)s4lV=l7M;f>vJl{`6qBm>#ZeMA}kXb97Z)?R97EkoI?x6Lp0yu1Z>PS?2{ z0QQ(8D)|lc9CO3B~e(pQM&5(1y&y=e>C^X$`)_&XuaI!IgDTVqt31wX#n+@!a_A0ZQkA zCJ2@M_4Gb5MfCrm5UPggeyh)8 zO9?`B0J#rkoCx(R0I!ko_2?iO@|oRf1;3r+i)w-2&j?=;NVIdPFsB)`|IC0zk6r9c zRrkfxWsiJ(#8QndNJj@{@WP2Ackr|r1VxV{7S&rSU(^)-M8gV>@UzOLXu9K<{6e{T zXJ6b92r$!|lwjhmgqkdswY&}c)KW4A)-ac%sU;2^fvq7gfUW4Bw$b!i@duy1CAxSn z(pyh$^Z=&O-q<{bZUP+$U}=*#M9uVc>CQVgDs4swy5&8RAHZ~$)hrTF4W zPsSa~qYv_0mJnF89RnnJTH`3}w4?~epFl=D(35$ zWa07ON$`OMBOHgCmfO(9RFc<)?$x)N}Jd2A(<*Ll7+4jrRt9w zwGxExUXd9VB#I|DwfxvJ;HZ8Q{37^wDhaZ%O!oO(HpcqfLH%#a#!~;Jl7F5>EX_=8 z{()l2NqPz>La3qJR;_v+wlK>GsHl;uRA8%j`A|yH@k5r%55S9{*Cp%uw6t`qc1!*T za2OeqtQj7sAp#Q~=5Fs&aCR9v>5V+s&RdNvo&H~6FJOjvaj--2sYYBvMq;55%z8^o z|BJDA4vzfow#DO#ZQHh;Oq_{r+qP{R9ox2TOgwQiv7Ow!zjN+A@BN;0tA2lUb#+zO z(^b89eV)D7UVE+h{mcNc6&GtpOqDn_?VAQ)Vob$hlFwW%xh>D#wml{t&Ofmm_d_+; zKDxzdr}`n2Rw`DtyIjrG)eD0vut$}dJAZ0AohZ+ZQdWXn_Z@dI_y=7t3q8x#pDI-K z2VVc&EGq445Rq-j0=U=Zx`oBaBjsefY;%)Co>J3v4l8V(T8H?49_@;K6q#r~Wwppc z4XW0(4k}cP=5ex>-Xt3oATZ~bBWKv)aw|I|Lx=9C1s~&b77idz({&q3T(Y(KbWO?+ zmcZ6?WeUsGk6>km*~234YC+2e6Zxdl~<_g2J|IE`GH%n<%PRv-50; zH{tnVts*S5*_RxFT9eM0z-pksIb^drUq4>QSww=u;UFCv2AhOuXE*V4z?MM`|ABOC4P;OfhS(M{1|c%QZ=!%rQTDFx`+}?Kdx$&FU?Y<$x;j7z=(;Lyz+?EE>ov!8vvMtSzG!nMie zsBa9t8as#2nH}n8xzN%W%U$#MHNXmDUVr@GX{?(=yI=4vks|V)!-W5jHsU|h_&+kY zS_8^kd3jlYqOoiI`ZqBVY!(UfnAGny!FowZWY_@YR0z!nG7m{{)4OS$q&YDyw6vC$ zm4!$h>*|!2LbMbxS+VM6&DIrL*X4DeMO!@#EzMVfr)e4Tagn~AQHIU8?e61TuhcKD zr!F4(kEebk(Wdk-?4oXM(rJwanS>Jc%<>R(siF+>+5*CqJLecP_we33iTFTXr6W^G z7M?LPC-qFHK;E!fxCP)`8rkxZyFk{EV;G-|kwf4b$c1k0atD?85+|4V%YATWMG|?K zLyLrws36p%Qz6{}>7b>)$pe>mR+=IWuGrX{3ZPZXF3plvuv5Huax86}KX*lbPVr}L z{C#lDjdDeHr~?l|)Vp_}T|%$qF&q#U;ClHEPVuS+Jg~NjC1RP=17=aQKGOcJ6B3mp z8?4*-fAD~}sX*=E6!}^u8)+m2j<&FSW%pYr_d|p_{28DZ#Cz0@NF=gC-o$MY?8Ca8 zr5Y8DSR^*urS~rhpX^05r30Ik#2>*dIOGxRm0#0YX@YQ%Mg5b6dXlS!4{7O_kdaW8PFSdj1=ryI-=5$fiieGK{LZ+SX(1b=MNL!q#lN zv98?fqqTUH8r8C7v(cx#BQ5P9W>- zmW93;eH6T`vuJ~rqtIBg%A6>q>gnWb3X!r0wh_q;211+Om&?nvYzL1hhtjB zK_7G3!n7PL>d!kj){HQE zE8(%J%dWLh1_k%gVXTZt zEdT09XSKAx27Ncaq|(vzL3gm83q>6CAw<$fTnMU05*xAe&rDfCiu`u^1)CD<>sx0i z*hr^N_TeN89G(nunZoLBf^81#pmM}>JgD@Nn1l*lN#a=B=9pN%tmvYFjFIoKe_(GF z-26x{(KXdfsQL7Uv6UtDuYwV`;8V3w>oT_I<`Ccz3QqK9tYT5ZQzbop{=I=!pMOCb zCU68`n?^DT%^&m>A%+-~#lvF!7`L7a{z<3JqIlk1$<||_J}vW1U9Y&eX<}l8##6i( zZcTT@2`9(Mecptm@{3A_Y(X`w9K0EwtPq~O!16bq{7c0f7#(3wn-^)h zxV&M~iiF!{-6A@>o;$RzQ5A50kxXYj!tcgme=Qjrbje~;5X2xryU;vH|6bE(8z^<7 zQ>BG7_c*JG8~K7Oe68i#0~C$v?-t@~@r3t2inUnLT(c=URpA9kA8uq9PKU(Ps(LVH zqgcqW>Gm?6oV#AldDPKVRcEyQIdTT`Qa1j~vS{<;SwyTdr&3*t?J)y=M7q*CzucZ&B0M=joT zBbj@*SY;o2^_h*>R0e({!QHF0=)0hOj^B^d*m>SnRrwq>MolNSgl^~r8GR#mDWGYEIJA8B<|{{j?-7p zVnV$zancW3&JVDtVpIlI|5djKq0(w$KxEFzEiiL=h5Jw~4Le23@s(mYyXWL9SX6Ot zmb)sZaly_P%BeX_9 zw&{yBef8tFm+%=--m*J|o~+Xg3N+$IH)t)=fqD+|fEk4AAZ&!wcN5=mi~Vvo^i`}> z#_3ahR}Ju)(Px7kev#JGcSwPXJ2id9%Qd2A#Uc@t8~egZ8;iC{e! z%=CGJOD1}j!HW_sgbi_8suYnn4#Ou}%9u)dXd3huFIb!ytlX>Denx@pCS-Nj$`VO&j@(z!kKSP0hE4;YIP#w9ta=3DO$7f*x zc9M4&NK%IrVmZAe=r@skWD`AEWH=g+r|*13Ss$+{c_R!b?>?UaGXlw*8qDmY#xlR= z<0XFbs2t?8i^G~m?b|!Hal^ZjRjt<@a? z%({Gn14b4-a|#uY^=@iiKH+k?~~wTj5K1A&hU z2^9-HTC)7zpoWK|$JXaBL6C z#qSNYtY>65T@Zs&-0cHeu|RX(Pxz6vTITdzJdYippF zC-EB+n4}#lM7`2Ry~SO>FxhKboIAF#Z{1wqxaCb{#yEFhLuX;Rx(Lz%T`Xo1+a2M}7D+@wol2)OJs$TwtRNJ={( zD@#zTUEE}#Fz#&(EoD|SV#bayvr&E0vzmb%H?o~46|FAcx?r4$N z&67W3mdip-T1RIxwSm_&(%U|+WvtGBj*}t69XVd&ebn>KOuL(7Y8cV?THd-(+9>G7*Nt%T zcH;`p={`SOjaf7hNd(=37Lz3-51;58JffzIPgGs_7xIOsB5p2t&@v1mKS$2D$*GQ6 zM(IR*j4{nri7NMK9xlDy-hJW6sW|ZiDRaFiayj%;(%51DN!ZCCCXz+0Vm#};70nOx zJ#yA0P3p^1DED;jGdPbQWo0WATN=&2(QybbVdhd=Vq*liDk`c7iZ?*AKEYC#SY&2g z&Q(Ci)MJ{mEat$ZdSwTjf6h~roanYh2?9j$CF@4hjj_f35kTKuGHvIs9}Re@iKMxS-OI*`0S z6s)fOtz}O$T?PLFVSeOjSO26$@u`e<>k(OSP!&YstH3ANh>)mzmKGNOwOawq-MPXe zy4xbeUAl6tamnx))-`Gi2uV5>9n(73yS)Ukma4*7fI8PaEwa)dWHs6QA6>$}7?(L8 ztN8M}?{Tf!Zu22J5?2@95&rQ|F7=FK-hihT-vDp!5JCcWrVogEnp;CHenAZ)+E+K5 z$Cffk5sNwD_?4+ymgcHR(5xgt20Z8M`2*;MzOM#>yhk{r3x=EyM226wb&!+j`W<%* zSc&|`8!>dn9D@!pYow~(DsY_naSx7(Z4i>cu#hA5=;IuI88}7f%)bRkuY2B;+9Uep zpXcvFWkJ!mQai63BgNXG26$5kyhZ2&*3Q_tk)Ii4M>@p~_~q_cE!|^A;_MHB;7s#9 zKzMzK{lIxotjc};k67^Xsl-gS!^*m*m6kn|sbdun`O?dUkJ{0cmI0-_2y=lTAfn*Y zKg*A-2sJq)CCJgY0LF-VQvl&6HIXZyxo2#!O&6fOhbHXC?%1cMc6y^*dOS{f$=137Ds1m01qs`>iUQ49JijsaQ( zksqV9@&?il$|4Ua%4!O15>Zy&%gBY&wgqB>XA3!EldQ%1CRSM(pp#k~-pkcCg4LAT zXE=puHbgsw)!xtc@P4r~Z}nTF=D2~j(6D%gTBw$(`Fc=OOQ0kiW$_RDd=hcO0t97h zb86S5r=>(@VGy1&#S$Kg_H@7G^;8Ue)X5Y+IWUi`o;mpvoV)`fcVk4FpcT|;EG!;? zHG^zrVVZOm>1KFaHlaogcWj(v!S)O(Aa|Vo?S|P z5|6b{qkH(USa*Z7-y_Uvty_Z1|B{rTS^qmEMLEYUSk03_Fg&!O3BMo{b^*`3SHvl0 zhnLTe^_vVIdcSHe)SQE}r~2dq)VZJ!aSKR?RS<(9lzkYo&dQ?mubnWmgMM37Nudwo z3Vz@R{=m2gENUE3V4NbIzAA$H1z0pagz94-PTJyX{b$yndsdKptmlKQKaaHj@3=ED zc7L?p@%ui|RegVYutK$64q4pe9+5sv34QUpo)u{1ci?)_7gXQd{PL>b0l(LI#rJmN zGuO+%GO`xneFOOr4EU(Wg}_%bhzUf;d@TU+V*2#}!2OLwg~%D;1FAu=Un>OgjPb3S z7l(riiCwgghC=Lm5hWGf5NdGp#01xQ59`HJcLXbUR3&n%P(+W2q$h2Qd z*6+-QXJ*&Kvk9ht0f0*rO_|FMBALen{j7T1l%=Q>gf#kma zQlg#I9+HB+z*5BMxdesMND`_W;q5|FaEURFk|~&{@qY32N$G$2B=&Po{=!)x5b!#n zxLzblkq{yj05#O7(GRuT39(06FJlalyv<#K4m}+vs>9@q-&31@1(QBv82{}Zkns~K ze{eHC_RDX0#^A*JQTwF`a=IkE6Ze@j#-8Q`tTT?k9`^ZhA~3eCZJ-Jr{~7Cx;H4A3 zcZ+Zj{mzFZbVvQ6U~n>$U2ZotGsERZ@}VKrgGh0xM;Jzt29%TX6_&CWzg+YYMozrM z`nutuS)_0dCM8UVaKRj804J4i%z2BA_8A4OJRQ$N(P9Mfn-gF;4#q788C@9XR0O3< zsoS4wIoyt046d+LnSCJOy@B@Uz*#GGd#+Ln1ek5Dv>(ZtD@tgZlPnZZJGBLr^JK+!$$?A_fA3LOrkoDRH&l7 zcMcD$Hsjko3`-{bn)jPL6E9Ds{WskMrivsUu5apD z?grQO@W7i5+%X&E&p|RBaEZ(sGLR@~(y^BI@lDMot^Ll?!`90KT!JXUhYS`ZgX3jnu@Ja^seA*M5R@f`=`ynQV4rc$uT1mvE?@tz)TN<=&H1%Z?5yjxcpO+6y_R z6EPuPKM5uxKpmZfT(WKjRRNHs@ib)F5WAP7QCADvmCSD#hPz$V10wiD&{NXyEwx5S z6NE`3z!IS^$s7m}PCwQutVQ#~w+V z=+~->DI*bR2j0^@dMr9`p>q^Ny~NrAVxrJtX2DUveic5vM%#N*XO|?YAWwNI$Q)_) zvE|L(L1jP@F%gOGtnlXtIv2&1i8q<)Xfz8O3G^Ea~e*HJsQgBxWL(yuLY+jqUK zRE~`-zklrGog(X}$9@ZVUw!8*=l`6mzYLtsg`AvBYz(cxmAhr^j0~(rzXdiOEeu_p zE$sf2(w(BPAvO5DlaN&uQ$4@p-b?fRs}d7&2UQ4Fh?1Hzu*YVjcndqJLw0#q@fR4u zJCJ}>_7-|QbvOfylj+e^_L`5Ep9gqd>XI3-O?Wp z-gt*P29f$Tx(mtS`0d05nHH=gm~Po_^OxxUwV294BDKT>PHVlC5bndncxGR!n(OOm znsNt@Q&N{TLrmsoKFw0&_M9$&+C24`sIXGWgQaz=kY;S{?w`z^Q0JXXBKFLj0w0U6P*+jPKyZHX9F#b0D1$&(- zrm8PJd?+SrVf^JlfTM^qGDK&-p2Kdfg?f>^%>1n8bu&byH(huaocL>l@f%c*QkX2i znl}VZ4R1en4S&Bcqw?$=Zi7ohqB$Jw9x`aM#>pHc0x z0$!q7iFu zZ`tryM70qBI6JWWTF9EjgG@>6SRzsd}3h+4D8d~@CR07P$LJ}MFsYi-*O%XVvD@yT|rJ+Mk zDllJ7$n0V&A!0flbOf)HE6P_afPWZmbhpliqJuw=-h+r;WGk|ntkWN(8tKlYpq5Ow z(@%s>IN8nHRaYb*^d;M(D$zGCv5C|uqmsDjwy4g=Lz>*OhO3z=)VD}C<65;`89Ye} zSCxrv#ILzIpEx1KdLPlM&%Cctf@FqTKvNPXC&`*H9=l=D3r!GLM?UV zOxa(8ZsB`&+76S-_xuj?G#wXBfDY@Z_tMpXJS7^mp z@YX&u0jYw2A+Z+bD#6sgVK5ZgdPSJV3>{K^4~%HV?rn~4D)*2H!67Y>0aOmzup`{D zzDp3c9yEbGCY$U<8biJ_gB*`jluz1ShUd!QUIQJ$*1;MXCMApJ^m*Fiv88RZ zFopLViw}{$Tyhh_{MLGIE2~sZ)t0VvoW%=8qKZ>h=adTe3QM$&$PO2lfqH@brt!9j ziePM8$!CgE9iz6B<6_wyTQj?qYa;eC^{x_0wuwV~W+^fZmFco-o%wsKSnjXFEx02V zF5C2t)T6Gw$Kf^_c;Ei3G~uC8SM-xyycmXyC2hAVi-IfXqhu$$-C=*|X?R0~hu z8`J6TdgflslhrmDZq1f?GXF7*ALeMmOEpRDg(s*H`4>_NAr`2uqF;k;JQ+8>A|_6ZNsNLECC%NNEb1Y1dP zbIEmNpK)#XagtL4R6BC{C5T(+=yA-(Z|Ap}U-AfZM#gwVpus3(gPn}Q$CExObJ5AC z)ff9Yk?wZ}dZ-^)?cbb9Fw#EjqQ8jxF4G3=L?Ra zg_)0QDMV1y^A^>HRI$x?Op@t;oj&H@1xt4SZ9(kifQ zb59B*`M99Td7@aZ3UWvj1rD0sE)d=BsBuW*KwkCds7ay(7*01_+L}b~7)VHI>F_!{ zyxg-&nCO?v#KOUec0{OOKy+sjWA;8rTE|Lv6I9H?CI?H(mUm8VXGwU$49LGpz&{nQp2}dinE1@lZ1iox6{ghN&v^GZv9J${7WaXj)<0S4g_uiJ&JCZ zr8-hsu`U%N;+9N^@&Q0^kVPB3)wY(rr}p7{p0qFHb3NUUHJb672+wRZs`gd1UjKPX z4o6zljKKA+Kkj?H>Ew63o%QjyBk&1!P22;MkD>sM0=z_s-G{mTixJCT9@_|*(p^bz zJ8?ZZ&;pzV+7#6Mn`_U-)k8Pjg?a;|Oe^us^PoPY$Va~yi8|?+&=y$f+lABT<*pZr zP}D{~Pq1Qyni+@|aP;ixO~mbEW9#c0OU#YbDZIaw=_&$K%Ep2f%hO^&P67hApZe`x zv8b`Mz@?M_7-)b!lkQKk)JXXUuT|B8kJlvqRmRpxtQDgvrHMXC1B$M@Y%Me!BSx3P z#2Eawl$HleZhhTS6Txm>lN_+I`>eV$&v9fOg)%zVn3O5mI*lAl>QcHuW6!Kixmq`X zBCZ*Ck6OYtDiK!N47>jxI&O2a9x7M|i^IagRr-fmrmikEQGgw%J7bO|)*$2FW95O4 zeBs>KR)izRG1gRVL;F*sr8A}aRHO0gc$$j&ds8CIO1=Gwq1%_~E)CWNn9pCtBE}+`Jelk4{>S)M)`Ll=!~gnn1yq^EX(+y*ik@3Ou0qU`IgYi3*doM+5&dU!cho$pZ zn%lhKeZkS72P?Cf68<#kll_6OAO26bIbueZx**j6o;I0cS^XiL`y+>{cD}gd%lux} z)3N>MaE24WBZ}s0ApfdM;5J_Ny}rfUyxfkC``Awo2#sgLnGPewK};dORuT?@I6(5~ z?kE)Qh$L&fwJXzK){iYx!l5$Tt|^D~MkGZPA}(o6f7w~O2G6Vvzdo*a;iXzk$B66$ zwF#;wM7A+(;uFG4+UAY(2`*3XXx|V$K8AYu#ECJYSl@S=uZW$ksfC$~qrrbQj4??z-)uz0QL}>k^?fPnJTPw% zGz)~?B4}u0CzOf@l^um}HZzbaIwPmb<)< zi_3@E9lc)Qe2_`*Z^HH;1CXOceL=CHpHS{HySy3T%<^NrWQ}G0i4e1xm_K3(+~oi$ zoHl9wzb?Z4j#90DtURtjtgvi7uw8DzHYmtPb;?%8vb9n@bszT=1qr)V_>R%s!92_` zfnHQPANx z<#hIjIMm#*(v*!OXtF+w8kLu`o?VZ5k7{`vw{Yc^qYclpUGIM_PBN1+c{#Vxv&E*@ zxg=W2W~JuV{IuRYw3>LSI1)a!thID@R=bU+cU@DbR^_SXY`MC7HOsCN z!dO4OKV7(E_Z8T#8MA1H`99?Z!r0)qKW_#|29X3#Jb+5+>qUidbeP1NJ@)(qi2S-X zao|f0_tl(O+$R|Qwd$H{_ig|~I1fbp_$NkI!0E;Y z6JrnU{1Ra6^on{9gUUB0mwzP3S%B#h0fjo>JvV~#+X0P~JV=IG=yHG$O+p5O3NUgG zEQ}z6BTp^Fie)Sg<){Z&I8NwPR(=mO4joTLHkJ>|Tnk23E(Bo`FSbPc05lF2-+)X? z6vV3*m~IBHTy*^E!<0nA(tCOJW2G4DsH7)BxLV8kICn5lu6@U*R`w)o9;Ro$i8=Q^V%uH8n3q=+Yf;SFRZu z!+F&PKcH#8cG?aSK_Tl@K9P#8o+jry@gdexz&d(Q=47<7nw@e@FFfIRNL9^)1i@;A z28+$Z#rjv-wj#heI|<&J_DiJ*s}xd-f!{J8jfqOHE`TiHHZVIA8CjkNQ_u;Ery^^t zl1I75&u^`1_q)crO+JT4rx|z2ToSC>)Or@-D zy3S>jW*sNIZR-EBsfyaJ+Jq4BQE4?SePtD2+jY8*%FsSLZ9MY>+wk?}}}AFAw)vr{ml)8LUG-y9>^t!{~|sgpxYc0Gnkg`&~R z-pilJZjr@y5$>B=VMdZ73svct%##v%wdX~9fz6i3Q-zOKJ9wso+h?VME7}SjL=!NUG{J?M&i!>ma`eoEa@IX`5G>B1(7;%}M*%-# zfhJ(W{y;>MRz!Ic8=S}VaBKqh;~7KdnGEHxcL$kA-6E~=!hrN*zw9N+_=odt<$_H_8dbo;0=42wcAETPCVGUr~v(`Uai zb{=D!Qc!dOEU6v)2eHSZq%5iqK?B(JlCq%T6av$Cb4Rko6onlG&?CqaX7Y_C_cOC3 zYZ;_oI(}=>_07}Oep&Ws7x7-R)cc8zfe!SYxJYP``pi$FDS)4Fvw5HH=FiU6xfVqIM!hJ;Rx8c0cB7~aPtNH(Nmm5Vh{ibAoU#J6 zImRCr?(iyu_4W_6AWo3*vxTPUw@vPwy@E0`(>1Qi=%>5eSIrp^`` zK*Y?fK_6F1W>-7UsB)RPC4>>Ps9)f+^MqM}8AUm@tZ->j%&h1M8s*s!LX5&WxQcAh z8mciQej@RPm?660%>{_D+7er>%zX_{s|$Z+;G7_sfNfBgY(zLB4Ey}J9F>zX#K0f6 z?dVNIeEh?EIShmP6>M+d|0wMM85Sa4diw1hrg|ITJ}JDg@o8y>(rF9mXk5M z2@D|NA)-7>wD&wF;S_$KS=eE84`BGw3g0?6wGxu8ys4rwI?9U=*^VF22t3%mbGeOh z`!O-OpF7#Vceu~F`${bW0nYVU9ecmk31V{tF%iv&5hWofC>I~cqAt@u6|R+|HLMMX zVxuSlMFOK_EQ86#E8&KwxIr8S9tj_goWtLv4f@!&h8;Ov41{J~496vp9vX=(LK#j! zAwi*21RAV-LD>9Cw3bV_9X(X3)Kr0-UaB*7Y>t82EQ%!)(&(XuAYtTsYy-dz+w=$ir)VJpe!_$ z6SGpX^i(af3{o=VlFPC);|J8#(=_8#vdxDe|Cok+ANhYwbE*FO`Su2m1~w+&9<_9~ z-|tTU_ACGN`~CNW5WYYBn^B#SwZ(t4%3aPp z;o)|L6Rk569KGxFLUPx@!6OOa+5OjQLK5w&nAmwxkC5rZ|m&HT8G%GVZxB_@ME z>>{rnXUqyiJrT(8GMj_ap#yN_!9-lO5e8mR3cJiK3NE{_UM&=*vIU`YkiL$1%kf+1 z4=jk@7EEj`u(jy$HnzE33ZVW_J4bj}K;vT?T91YlO(|Y0FU4r+VdbmQ97%(J5 zkK*Bed8+C}FcZ@HIgdCMioV%A<*4pw_n}l*{Cr4}a(lq|injK#O?$tyvyE`S%(1`H z_wwRvk#13ElkZvij2MFGOj`fhy?nC^8`Zyo%yVcUAfEr8x&J#A{|moUBAV_^f$hpaUuyQeY3da^ zS9iRgf87YBwfe}>BO+T&Fl%rfpZh#+AM?Dq-k$Bq`vG6G_b4z%Kbd&v>qFjow*mBl z-OylnqOpLg}or7_VNwRg2za3VBK6FUfFX{|TD z`Wt0Vm2H$vdlRWYQJqDmM?JUbVqL*ZQY|5&sY*?!&%P8qhA~5+Af<{MaGo(dl&C5t zE%t!J0 zh6jqANt4ABdPxSTrVV}fLsRQal*)l&_*rFq(Ez}ClEH6LHv{J#v?+H-BZ2)Wy{K@9 z+ovXHq~DiDvm>O~r$LJo!cOuwL+Oa--6;UFE2q@g3N8Qkw5E>ytz^(&($!O47+i~$ zKM+tkAd-RbmP{s_rh+ugTD;lriL~`Xwkad#;_aM?nQ7L_muEFI}U_4$phjvYgleK~`Fo`;GiC07&Hq1F<%p;9Q;tv5b?*QnR%8DYJH3P>Svmv47Y>*LPZJy8_{9H`g6kQpyZU{oJ`m%&p~D=K#KpfoJ@ zn-3cqmHsdtN!f?~w+(t+I`*7GQA#EQC^lUA9(i6=i1PqSAc|ha91I%X&nXzjYaM{8$s&wEx@aVkQ6M{E2 zfzId#&r(XwUNtPcq4Ngze^+XaJA1EK-%&C9j>^9(secqe{}z>hR5CFNveMsVA)m#S zk)_%SidkY-XmMWlVnQ(mNJ>)ooszQ#vaK;!rPmGKXV7am^_F!Lz>;~{VrIO$;!#30XRhE1QqO_~#+Ux;B_D{Nk=grn z8Y0oR^4RqtcYM)7a%@B(XdbZCOqnX#fD{BQTeLvRHd(irHKq=4*jq34`6@VAQR8WG z^%)@5CXnD_T#f%@-l${>y$tfb>2LPmc{~5A82|16mH)R?&r#KKLs7xpN-D`=&Cm^R zvMA6#Ahr<3X>Q7|-qfTY)}32HkAz$_mibYV!I)u>bmjK`qwBe(>za^0Kt*HnFbSdO z1>+ryKCNxmm^)*$XfiDOF2|{-v3KKB?&!(S_Y=Ht@|ir^hLd978xuI&N{k>?(*f8H z=ClxVJK_%_z1TH0eUwm2J+2To7FK4o+n_na)&#VLn1m;!+CX+~WC+qg1?PA~KdOlC zW)C@pw75_xoe=w7i|r9KGIvQ$+3K?L{7TGHwrQM{dCp=Z*D}3kX7E-@sZnup!BImw z*T#a=+WcTwL78exTgBn|iNE3#EsOorO z*kt)gDzHiPt07fmisA2LWN?AymkdqTgr?=loT7z@d`wnlr6oN}@o|&JX!yPzC*Y8d zu6kWlTzE1)ckyBn+0Y^HMN+GA$wUO_LN6W>mxCo!0?oiQvT`z$jbSEu&{UHRU0E8# z%B^wOc@S!yhMT49Y)ww(Xta^8pmPCe@eI5C*ed96)AX9<>))nKx0(sci8gwob_1}4 z0DIL&vsJ1_s%<@y%U*-eX z5rN&(zef-5G~?@r79oZGW1d!WaTqQn0F6RIOa9tJ=0(kdd{d1{<*tHT#cCvl*i>YY zH+L7jq8xZNcTUBqj(S)ztTU!TM!RQ}In*n&Gn<>(60G7}4%WQL!o>hbJqNDSGwl#H z`4k+twp0cj%PsS+NKaxslAEu9!#U3xT1|_KB6`h=PI0SW`P9GTa7caD1}vKEglV8# zjKZR`pluCW19c2fM&ZG)c3T3Um;ir3y(tSCJ7Agl6|b524dy5El{^EQBG?E61H0XY z`bqg!;zhGhyMFl&(o=JWEJ8n~z)xI}A@C0d2hQGvw7nGv)?POU@(kS1m=%`|+^ika zXl8zjS?xqW$WlO?Ewa;vF~XbybHBor$f<%I&*t$F5fynwZlTGj|IjZtVfGa7l&tK} zW>I<69w(cZLu)QIVG|M2xzW@S+70NinQzk&Y0+3WT*cC)rx~04O-^<{JohU_&HL5XdUKW!uFy|i$FB|EMu0eUyW;gsf`XfIc!Z0V zeK&*hPL}f_cX=@iv>K%S5kL;cl_$v?n(Q9f_cChk8Lq$glT|=e+T*8O4H2n<=NGmn z+2*h+v;kBvF>}&0RDS>)B{1!_*XuE8A$Y=G8w^qGMtfudDBsD5>T5SB;Qo}fSkkiV ze^K^M(UthkwrD!&*tTsu>Dacdj_q`~V%r_twr$(Ct&_dKeeXE?fA&4&yASJWJ*}~- zel=@W)tusynfC_YqH4ll>4Eg`Xjs5F7Tj>tTLz<0N3)X<1px_d2yUY>X~y>>93*$) z5PuNMQLf9Bu?AAGO~a_|J2akO1M*@VYN^VxvP0F$2>;Zb9;d5Yfd8P%oFCCoZE$ z4#N$^J8rxYjUE_6{T%Y>MmWfHgScpuGv59#4u6fpTF%~KB^Ae`t1TD_^Ud#DhL+Dm zbY^VAM#MrAmFj{3-BpVSWph2b_Y6gCnCAombVa|1S@DU)2r9W<> zT5L8BB^er3zxKt1v(y&OYk!^aoQisqU zH(g@_o)D~BufUXcPt!Ydom)e|aW{XiMnes2z&rE?og>7|G+tp7&^;q?Qz5S5^yd$i z8lWr4g5nctBHtigX%0%XzIAB8U|T6&JsC4&^hZBw^*aIcuNO47de?|pGXJ4t}BB`L^d8tD`H`i zqrP8?#J@8T#;{^B!KO6J=@OWKhAerih(phML`(Rg7N1XWf1TN>=Z3Do{l_!d~DND&)O)D>ta20}@Lt77qSnVsA7>)uZAaT9bsB>u&aUQl+7GiY2|dAEg@%Al3i316y;&IhQL^8fw_nwS>f60M_-m+!5)S_6EPM7Y)(Nq^8gL7(3 zOiot`6Wy6%vw~a_H?1hLVzIT^i1;HedHgW9-P#)}Y6vF%C=P70X0Tk^z9Te@kPILI z_(gk!k+0%CG)%!WnBjjw*kAKs_lf#=5HXC00s-}oM-Q1aXYLj)(1d!_a7 z*Gg4Fe6F$*ujVjI|79Z5+Pr`us%zW@ln++2l+0hsngv<{mJ%?OfSo_3HJXOCys{Ug z00*YR-(fv<=&%Q!j%b-_ppA$JsTm^_L4x`$k{VpfLI(FMCap%LFAyq;#ns5bR7V+x zO!o;c5y~DyBPqdVQX)8G^G&jWkBy2|oWTw>)?5u}SAsI$RjT#)lTV&Rf8;>u*qXnb z8F%Xb=7#$m)83z%`E;49)t3fHInhtc#kx4wSLLms!*~Z$V?bTyUGiS&m>1P(952(H zuHdv=;o*{;5#X-uAyon`hP}d#U{uDlV?W?_5UjJvf%11hKwe&(&9_~{W)*y1nR5f_ z!N(R74nNK`y8>B!0Bt_Vr!;nc3W>~RiKtGSBkNlsR#-t^&;$W#)f9tTlZz>n*+Fjz z3zXZ;jf(sTM(oDzJt4FJS*8c&;PLTW(IQDFs_5QPy+7yhi1syPCarvqrHFcf&yTy)^O<1EBx;Ir`5W{TIM>{8w&PB>ro4;YD<5LF^TjTb0!zAP|QijA+1Vg>{Afv^% zmrkc4o6rvBI;Q8rj4*=AZacy*n8B{&G3VJc)so4$XUoie0)vr;qzPZVbb<#Fc=j+8CGBWe$n|3K& z_@%?{l|TzKSlUEO{U{{%Fz_pVDxs7i9H#bnbCw7@4DR=}r_qV!Zo~CvD4ZI*+j3kO zW6_=|S`)(*gM0Z;;}nj`73OigF4p6_NPZQ-Od~e$c_);;4-7sR>+2u$6m$Gf%T{aq zle>e3(*Rt(TPD}03n5)!Ca8Pu!V}m6v0o1;5<1h$*|7z|^(3$Y&;KHKTT}hV056wuF0Xo@mK-52~r=6^SI1NC%c~CC?n>yX6wPTgiWYVz!Sx^atLby9YNn1Rk{g?|pJaxD4|9cUf|V1_I*w zzxK)hRh9%zOl=*$?XUjly5z8?jPMy%vEN)f%T*|WO|bp5NWv@B(K3D6LMl!-6dQg0 zXNE&O>Oyf%K@`ngCvbGPR>HRg5!1IV$_}m@3dWB7x3t&KFyOJn9pxRXCAzFr&%37wXG;z^xaO$ekR=LJG ztIHpY8F5xBP{mtQidqNRoz= z@){+N3(VO5bD+VrmS^YjG@+JO{EOIW)9=F4v_$Ed8rZtHvjpiEp{r^c4F6Ic#ChlC zJX^DtSK+v(YdCW)^EFcs=XP7S>Y!4=xgmv>{S$~@h=xW-G4FF9?I@zYN$e5oF9g$# zb!eVU#J+NjLyX;yb)%SY)xJdvGhsnE*JEkuOVo^k5PyS=o#vq!KD46UTW_%R=Y&0G zFj6bV{`Y6)YoKgqnir2&+sl+i6foAn-**Zd1{_;Zb7Ki=u394C5J{l^H@XN`_6XTKY%X1AgQM6KycJ+= zYO=&t#5oSKB^pYhNdzPgH~aEGW2=ec1O#s-KG z71}LOg@4UEFtp3GY1PBemXpNs6UK-ax*)#$J^pC_me;Z$Je(OqLoh|ZrW*mAMBFn< zHttjwC&fkVfMnQeen8`Rvy^$pNRFVaiEN4Pih*Y3@jo!T0nsClN)pdrr9AYLcZxZ| zJ5Wlj+4q~($hbtuY zVQ7hl>4-+@6g1i`1a)rvtp-;b0>^`Dloy(#{z~ytgv=j4q^Kl}wD>K_Y!l~ zp(_&7sh`vfO(1*MO!B%<6E_bx1)&s+Ae`O)a|X=J9y~XDa@UB`m)`tSG4AUhoM=5& znWoHlA-(z@3n0=l{E)R-p8sB9XkV zZ#D8wietfHL?J5X0%&fGg@MH~(rNS2`GHS4xTo7L$>TPme+Is~!|79=^}QbPF>m%J zFMkGzSndiPO|E~hrhCeo@&Ea{M(ieIgRWMf)E}qeTxT8Q#g-!Lu*x$v8W^M^>?-g= zwMJ$dThI|~M06rG$Sv@C@tWR>_YgaG&!BAbkGggVQa#KdtDB)lMLNVLN|51C@F^y8 zCRvMB^{GO@j=cHfmy}_pCGbP%xb{pNN>? z?7tBz$1^zVaP|uaatYaIN+#xEN4jBzwZ|YI_)p(4CUAz1ZEbDk>J~Y|63SZaak~#0 zoYKruYsWHoOlC1(MhTnsdUOwQfz5p6-D0}4;DO$B;7#M{3lSE^jnTT;ns`>!G%i*F?@pR1JO{QTuD0U+~SlZxcc8~>IB{)@8p`P&+nDxNj`*gh|u?yrv$phpQcW)Us)bi`kT%qLj(fi{dWRZ%Es2!=3mI~UxiW0$-v3vUl?#g{p6eF zMEUAqo5-L0Ar(s{VlR9g=j7+lt!gP!UN2ICMokAZ5(Agd>})#gkA2w|5+<%-CuEP# zqgcM}u@3(QIC^Gx<2dbLj?cFSws_f3e%f4jeR?4M^M3cx1f+Qr6ydQ>n)kz1s##2w zk}UyQc+Z5G-d-1}{WzjkLXgS-2P7auWSJ%pSnD|Uivj5u!xk0 z_^-N9r9o;(rFDt~q1PvE#iJZ_f>J3gcP$)SOqhE~pD2|$=GvpL^d!r z6u=sp-CrMoF7;)}Zd7XO4XihC4ji?>V&(t^?@3Q&t9Mx=qex6C9d%{FE6dvU6%d94 zIE;hJ1J)cCqjv?F``7I*6bc#X)JW2b4f$L^>j{*$R`%5VHFi*+Q$2;nyieduE}qdS{L8y8F08yLs?w}{>8>$3236T-VMh@B zq-nujsb_1aUv_7g#)*rf9h%sFj*^mIcImRV*k~Vmw;%;YH(&ylYpy!&UjUVqqtfG` zox3esju?`unJJA_zKXRJP)rA3nXc$m^{S&-p|v|-0x9LHJm;XIww7C#R$?00l&Yyj z=e}gKUOpsImwW?N)+E(awoF@HyP^EhL+GlNB#k?R<2>95hz!h9sF@U20DHSB3~WMa zk90+858r@-+vWwkawJ)8ougd(i#1m3GLN{iSTylYz$brAsP%=&m$mQQrH$g%3-^VR zE%B`Vi&m8f3T~&myTEK28BDWCVzfWir1I?03;pX))|kY5ClO^+bae z*7E?g=3g7EiisYOrE+lA)2?Ln6q2*HLNpZEWMB|O-JI_oaHZB%CvYB(%=tU= zE*OY%QY58fW#RG5=gm0NR#iMB=EuNF@)%oZJ}nmm=tsJ?eGjia{e{yuU0l3{d^D@)kVDt=1PE)&tf_hHC%0MB znL|CRCPC}SeuVTdf>-QV70`0(EHizc21s^sU>y%hW0t!0&y<7}Wi-wGy>m%(-jsDj zP?mF|>p_K>liZ6ZP(w5(|9Ga%>tLgb$|doDDfkdW>Z z`)>V2XC?NJT26mL^@ zf+IKr27TfM!UbZ@?zRddC7#6ss1sw%CXJ4FWC+t3lHZupzM77m^=9 z&(a?-LxIq}*nvv)y?27lZ{j zifdl9hyJudyP2LpU$-kXctshbJDKS{WfulP5Dk~xU4Le4c#h^(YjJit4#R8_khheS z|8(>2ibaHES4+J|DBM7I#QF5u-*EdN{n=Kt@4Zt?@Tv{JZA{`4 zU#kYOv{#A&gGPwT+$Ud}AXlK3K7hYzo$(fBSFjrP{QQ zeaKg--L&jh$9N}`pu{Bs>?eDFPaWY4|9|foN%}i;3%;@4{dc+iw>m}{3rELqH21G! z`8@;w-zsJ1H(N3%|1B@#ioLOjib)j`EiJqPQVSbPSPVHCj6t5J&(NcWzBrzCiDt{4 zdlPAUKldz%6x5II1H_+jv)(xVL+a;P+-1hv_pM>gMRr%04@k;DTokASSKKhU1Qms| zrWh3a!b(J3n0>-tipg{a?UaKsP7?+|@A+1WPDiQIW1Sf@qDU~M_P65_s}7(gjTn0X zucyEm)o;f8UyshMy&>^SC3I|C6jR*R_GFwGranWZe*I>K+0k}pBuET&M~ z;Odo*ZcT?ZpduHyrf8E%IBFtv;JQ!N_m>!sV6ly$_1D{(&nO~w)G~Y`7sD3#hQk%^ zp}ucDF_$!6DAz*PM8yE(&~;%|=+h(Rn-=1Wykas_-@d&z#=S}rDf`4w(rVlcF&lF! z=1)M3YVz7orwk^BXhslJ8jR);sh^knJW(Qmm(QdSgIAIdlN4Te5KJisifjr?eB{FjAX1a0AB>d?qY4Wx>BZ8&}5K0fA+d{l8 z?^s&l8#j7pR&ijD?0b%;lL9l$P_mi2^*_OL+b}4kuLR$GAf85sOo02?Y#90}CCDiS zZ%rbCw>=H~CBO=C_JVV=xgDe%b4FaEFtuS7Q1##y686r%F6I)s-~2(}PWK|Z8M+Gu zl$y~5@#0Ka%$M<&Cv%L`a8X^@tY&T7<0|(6dNT=EsRe0%kp1Qyq!^43VAKYnr*A5~ zsI%lK1ewqO;0TpLrT9v}!@vJK{QoVa_+N4FYT#h?Y8rS1S&-G+m$FNMP?(8N`MZP zels(*?kK{{^g9DOzkuZXJ2;SrOQsp9T$hwRB1(phw1c7`!Q!by?Q#YsSM#I12RhU{$Q+{xj83axHcftEc$mNJ8_T7A-BQc*k(sZ+~NsO~xAA zxnbb%dam_fZlHvW7fKXrB~F&jS<4FD2FqY?VG?ix*r~MDXCE^WQ|W|WM;gsIA4lQP zJ2hAK@CF*3*VqPr2eeg6GzWFlICi8S>nO>5HvWzyZTE)hlkdC_>pBej*>o0EOHR|) z$?};&I4+_?wvL*g#PJ9)!bc#9BJu1(*RdNEn>#Oxta(VWeM40ola<0aOe2kSS~{^P zDJBd}0L-P#O-CzX*%+$#v;(x%<*SPgAje=F{Zh-@ucd2DA(yC|N_|ocs*|-!H%wEw z@Q!>siv2W;C^^j^59OAX03&}&D*W4EjCvfi(ygcL#~t8XGa#|NPO+*M@Y-)ctFA@I z-p7npT1#5zOLo>7q?aZpCZ=iecn3QYklP;gF0bq@>oyBq94f6C=;Csw3PkZ|5q=(c zfs`aw?II0e(h=|7o&T+hq&m$; zBrE09Twxd9BJ2P+QPN}*OdZ-JZV7%av@OM7v!!NL8R;%WFq*?{9T3{ct@2EKgc8h) zMxoM$SaF#p<`65BwIDfmXG6+OiK0e)`I=!A3E`+K@61f}0e z!2a*FOaDrOe>U`q%K!QN`&=&0C~)CaL3R4VY(NDt{Xz(Xpqru5=r#uQN1L$Je1*dkdqQ*=lofQaN%lO!<5z9ZlHgxt|`THd>2 zsWfU$9=p;yLyJyM^t zS2w9w?Bpto`@H^xJpZDKR1@~^30Il6oFGfk5%g6w*C+VM)+%R@gfIwNprOV5{F^M2 zO?n3DEzpT+EoSV-%OdvZvNF+pDd-ZVZ&d8 zKeIyrrfPN=EcFRCPEDCVflX#3-)Ik_HCkL(ejmY8vzcf-MTA{oHk!R2*36`O68$7J zf}zJC+bbQk--9Xm!u#lgLvx8TXx2J258E5^*IZ(FXMpq$2LUUvhWQPs((z1+2{Op% z?J}9k5^N=z;7ja~zi8a_-exIqWUBJwohe#4QJ`|FF*$C{lM18z^#hX6!5B8KAkLUX ziP=oti-gpV(BsLD{0(3*dw}4JxK23Y7M{BeFPucw!sHpY&l%Ws4pSm`+~V7;bZ%Dx zeI)MK=4vC&5#;2MT7fS?^ch9?2;%<8Jlu-IB&N~gg8t;6S-#C@!NU{`p7M8@2iGc& zg|JPg%@gCoCQ&s6JvDU&`X2S<57f(k8nJ1wvBu{8r?;q3_kpZZ${?|( z+^)UvR33sjSd)aT!UPkA;ylO6{aE3MQa{g%Mcf$1KONcjO@&g5zPHWtzM1rYC{_K> zgQNcs<{&X{OA=cEWw5JGqpr0O>x*Tfak2PE9?FuWtz^DDNI}rwAaT0(bdo-<+SJ6A z&}S%boGMWIS0L}=S>|-#kRX;e^sUsotry(MjE|3_9duvfc|nwF#NHuM-w7ZU!5ei8 z6Mkf>2)WunY2eU@C-Uj-A zG(z0Tz2YoBk>zCz_9-)4a>T46$(~kF+Y{#sA9MWH%5z#zNoz)sdXq7ZR_+`RZ%0(q zC7&GyS_|BGHNFl8Xa%@>iWh%Gr?=J5<(!OEjauj5jyrA-QXBjn0OAhJJ9+v=!LK`` z@g(`^*84Q4jcDL`OA&ZV60djgwG`|bcD*i50O}Q{9_noRg|~?dj%VtKOnyRs$Uzqg z191aWoR^rDX#@iSq0n z?9Sg$WSRPqSeI<}&n1T3!6%Wj@5iw5`*`Btni~G=&;J+4`7g#OQTa>u`{4ZZ(c@s$ zK0y;ySOGD-UTjREKbru{QaS>HjN<2)R%Nn-TZiQ(Twe4p@-saNa3~p{?^V9Nixz@a zykPv~<@lu6-Ng9i$Lrk(xi2Tri3q=RW`BJYOPC;S0Yly%77c727Yj-d1vF!Fuk{Xh z)lMbA69y7*5ufET>P*gXQrxsW+ zz)*MbHZv*eJPEXYE<6g6_M7N%#%mR{#awV3i^PafNv(zyI)&bH?F}2s8_rR(6%!V4SOWlup`TKAb@ee>!9JKPM=&8g#BeYRH9FpFybxBXQI2|g}FGJfJ+ zY-*2hB?o{TVL;Wt_ek;AP5PBqfDR4@Z->_182W z{P@Mc27j6jE*9xG{R$>6_;i=y{qf(c`5w9fa*`rEzX6t!KJ(p1H|>J1pC-2zqWENF zmm=Z5B4u{cY2XYl(PfrInB*~WGWik3@1oRhiMOS|D;acnf-Bs(QCm#wR;@Vf!hOPJ zgjhDCfDj$HcyVLJ=AaTbQ{@vIv14LWWF$=i-BDoC11}V;2V8A`S>_x)vIq44-VB-v z*w-d}$G+Ql?En8j!~ZkCpQ$|cA0|+rrY>tiCeWxkRGPoarxlGU2?7%k#F693RHT24 z-?JsiXlT2PTqZqNb&sSc>$d;O4V@|b6VKSWQb~bUaWn1Cf0+K%`Q&Wc<>mQ>*iEGB zbZ;aYOotBZ{vH3y<0A*L0QVM|#rf*LIsGx(O*-7)r@yyBIzJnBFSKBUSl1e|8lxU* zzFL+YDVVkIuzFWeJ8AbgN&w(4-7zbiaMn{5!JQXu)SELk*CNL+Fro|2v|YO)1l15t zs(0^&EB6DPMyaqvY>=KL>)tEpsn;N5Q#yJj<9}ImL((SqErWN3Q=;tBO~ExTCs9hB z2E$7eN#5wX4<3m^5pdjm#5o>s#eS_Q^P)tm$@SawTqF*1dj_i#)3};JslbLKHXl_N z)Fxzf>FN)EK&Rz&*|6&%Hs-^f{V|+_vL1S;-1K-l$5xiC@}%uDuwHYhmsV?YcOUlk zOYkG5v2+`+UWqpn0aaaqrD3lYdh0*!L`3FAsNKu=Q!vJu?Yc8n|CoYyDo_`r0mPoo z8>XCo$W4>l(==h?2~PoRR*kEe)&IH{1sM41mO#-36`02m#nTX{r*r`Q5rZ2-sE|nA zhnn5T#s#v`52T5|?GNS`%HgS2;R(*|^egNPDzzH_z^W)-Q98~$#YAe)cEZ%vge965AS_am#DK#pjPRr-!^za8>`kksCAUj(Xr*1NW5~e zpypt_eJpD&4_bl_y?G%>^L}=>xAaV>KR6;^aBytqpiHe%!j;&MzI_>Sx7O%F%D*8s zSN}cS^<{iiK)=Ji`FpO#^zY!_|D)qeRNAtgmH)m;qC|mq^j(|hL`7uBz+ULUj37gj zksdbnU+LSVo35riSX_4z{UX=%n&}7s0{WuZYoSfwAP`8aKN9P@%e=~1`~1ASL-z%# zw>DO&ixr}c9%4InGc*_y42bdEk)ZdG7-mTu0bD@_vGAr*NcFoMW;@r?@LUhRI zCUJgHb`O?M3!w)|CPu~ej%fddw20lod?Ufp8Dmt0PbnA0J%KE^2~AIcnKP()025V> zG>noSM3$5Btmc$GZoyP^v1@Poz0FD(6YSTH@aD0}BXva?LphAiSz9f&Y(aDAzBnUh z?d2m``~{z;{}kZJ>a^wYI?ry(V9hIoh;|EFc0*-#*`$T0DRQ1;WsqInG;YPS+I4{g zJGpKk%%Sdc5xBa$Q^_I~(F97eqDO7AN3EN0u)PNBAb+n+ zWBTxQx^;O9o0`=g+Zrt_{lP!sgWZHW?8bLYS$;1a@&7w9rD9|Ge;Gb?sEjFoF9-6v z#!2)t{DMHZ2@0W*fCx;62d#;jouz`R5Y(t{BT=$N4yr^^o$ON8d{PQ=!O zX17^CrdM~7D-;ZrC!||<+FEOxI_WI3CA<35va%4v>gc zEX-@h8esj=a4szW7x{0g$hwoWRQG$yK{@3mqd-jYiVofJE!Wok1* znV7Gm&Ssq#hFuvj1sRyHg(6PFA5U*Q8Rx>-blOs=lb`qa{zFy&n4xY;sd$fE+<3EI z##W$P9M{B3c3Si9gw^jlPU-JqD~Cye;wr=XkV7BSv#6}DrsXWFJ3eUNrc%7{=^sP> zrp)BWKA9<}^R9g!0q7yWlh;gr_TEOD|#BmGq<@IV;ueg+D2}cjpp+dPf&Q(36sFU&K8}hA85U61faW&{ zlB`9HUl-WWCG|<1XANN3JVAkRYvr5U4q6;!G*MTdSUt*Mi=z_y3B1A9j-@aK{lNvx zK%p23>M&=KTCgR!Ee8c?DAO2_R?B zkaqr6^BSP!8dHXxj%N1l+V$_%vzHjqvu7p@%Nl6;>y*S}M!B=pz=aqUV#`;h%M0rU zHfcog>kv3UZAEB*g7Er@t6CF8kHDmKTjO@rejA^ULqn!`LwrEwOVmHx^;g|5PHm#B zZ+jjWgjJ!043F+&#_;D*mz%Q60=L9Ove|$gU&~As5^uz@2-BfQ!bW)Khn}G+Wyjw- z19qI#oB(RSNydn0t~;tAmK!P-d{b-@@E5|cdgOS#!>%#Rj6ynkMvaW@37E>@hJP^8 z2zk8VXx|>#R^JCcWdBCy{0nPmYFOxN55#^-rlqobe0#L6)bi?E?SPymF*a5oDDeSd zO0gx?#KMoOd&G(2O@*W)HgX6y_aa6iMCl^~`{@UR`nMQE`>n_{_aY5nA}vqU8mt8H z`oa=g0SyiLd~BxAj2~l$zRSDHxvDs;I4>+M$W`HbJ|g&P+$!U7-PHX4RAcR0szJ*( ze-417=bO2q{492SWrqDK+L3#ChUHtz*@MP)e^%@>_&#Yk^1|tv@j4%3T)diEX zATx4K*hcO`sY$jk#jN5WD<=C3nvuVsRh||qDHnc~;Kf59zr0;c7VkVSUPD%NnnJC_ zl3F^#f_rDu8l}l8qcAz0FFa)EAt32IUy_JLIhU_J^l~FRH&6-ivSpG2PRqzDdMWft>Zc(c)#tb%wgmWN%>IOPm zZi-noqS!^Ftb81pRcQi`X#UhWK70hy4tGW1mz|+vI8c*h@ zfFGJtW3r>qV>1Z0r|L>7I3un^gcep$AAWfZHRvB|E*kktY$qQP_$YG60C@X~tTQjB3%@`uz!qxtxF+LE!+=nrS^07hn` zEgAp!h|r03h7B!$#OZW#ACD+M;-5J!W+{h|6I;5cNnE(Y863%1(oH}_FTW})8zYb$7czP zg~Szk1+_NTm6SJ0MS_|oSz%e(S~P-&SFp;!k?uFayytV$8HPwuyELSXOs^27XvK-D zOx-Dl!P|28DK6iX>p#Yb%3`A&CG0X2S43FjN%IB}q(!hC$fG}yl1y9W&W&I@KTg6@ zK^kpH8=yFuP+vI^+59|3%Zqnb5lTDAykf z9S#X`3N(X^SpdMyWQGOQRjhiwlj!0W-yD<3aEj^&X%=?`6lCy~?`&WSWt z?U~EKFcCG_RJ(Qp7j=$I%H8t)Z@6VjA#>1f@EYiS8MRHZphp zMA_5`znM=pzUpBPO)pXGYpQ6gkine{6u_o!P@Q+NKJ}k!_X7u|qfpAyIJb$_#3@wJ z<1SE2Edkfk9C!0t%}8Yio09^F`YGzpaJHGk*-ffsn85@)%4@`;Fv^8q(-Wk7r=Q8p zT&hD`5(f?M{gfzGbbwh8(}G#|#fDuk7v1W)5H9wkorE0ZZjL0Q1=NRGY>zwgfm81DdoaVwNH;or{{eSyybt)m<=zXoA^RALYG-2t zouH|L*BLvmm9cdMmn+KGopyR@4*=&0&4g|FLoreZOhRmh=)R0bg~ zT2(8V_q7~42-zvb)+y959OAv!V$u(O3)%Es0M@CRFmG{5sovIq4%8Ahjk#*5w{+)+ zMWQoJI_r$HxL5km1#6(e@{lK3Udc~n0@g`g$s?VrnQJ$!oPnb?IHh-1qA`Rz$)Ai< z6w$-MJW-gKNvOhL+XMbE7&mFt`x1KY>k4(!KbbpZ`>`K@1J<(#vVbjx@Z@(6Q}MF# zMnbr-f55(cTa^q4+#)=s+ThMaV~E`B8V=|W_fZWDwiso8tNMTNse)RNBGi=gVwgg% zbOg8>mbRN%7^Um-7oj4=6`$|(K7!+t^90a{$18Z>}<#!bm%ZEFQ{X(yBZMc>lCz0f1I2w9Sq zuGh<9<=AO&g6BZte6hn>Qmvv;Rt)*cJfTr2=~EnGD8P$v3R|&1RCl&7)b+`=QGapi zPbLg_pxm`+HZurtFZ;wZ=`Vk*do~$wB zxoW&=j0OTbQ=Q%S8XJ%~qoa3Ea|au5o}_(P;=!y-AjFrERh%8la!z6Fn@lR?^E~H12D?8#ht=1F;7@o4$Q8GDj;sSC%Jfn01xgL&%F2 zwG1|5ikb^qHv&9hT8w83+yv&BQXOQyMVJSBL(Ky~p)gU3#%|blG?IR9rP^zUbs7rOA0X52Ao=GRt@C&zlyjNLv-} z9?*x{y(`509qhCV*B47f2hLrGl^<@SuRGR!KwHei?!CM10Tq*YDIoBNyRuO*>3FU? zHjipIE#B~y3FSfOsMfj~F9PNr*H?0oHyYB^G(YyNh{SxcE(Y-`x5jFMKb~HO*m+R% zrq|ic4fzJ#USpTm;X7K+E%xsT_3VHKe?*uc4-FsILUH;kL>_okY(w`VU*8+l>o>Jm ziU#?2^`>arnsl#)*R&nf_%>A+qwl%o{l(u)M?DK1^mf260_oteV3#E_>6Y4!_hhVD zM8AI6MM2V*^_M^sQ0dmHu11fy^kOqXqzpr?K$`}BKWG`=Es(9&S@K@)ZjA{lj3ea7_MBP zk(|hBFRjHVMN!sNUkrB;(cTP)T97M$0Dtc&UXSec<+q?y>5=)}S~{Z@ua;1xt@=T5 zI7{`Z=z_X*no8s>mY;>BvEXK%b`a6(DTS6t&b!vf_z#HM{Uoy_5fiB(zpkF{})ruka$iX*~pq1ZxD?q68dIo zIZSVls9kFGsTwvr4{T_LidcWtt$u{kJlW7moRaH6+A5hW&;;2O#$oKyEN8kx`LmG)Wfq4ykh+q{I3|RfVpkR&QH_x;t41Uw z`P+tft^E2B$domKT@|nNW`EHwyj>&}K;eDpe z1bNOh=fvIfk`&B61+S8ND<(KC%>y&?>opCnY*r5M+!UrWKxv0_QvTlJc>X#AaI^xo zaRXL}t5Ej_Z$y*|w*$6D+A?Lw-CO-$itm^{2Ct82-<0IW)0KMNvJHgBrdsIR0v~=H z?n6^}l{D``Me90`^o|q!olsF?UX3YSq^6Vu>Ijm>>PaZI8G@<^NGw{Cx&%|PwYrfw zR!gX_%AR=L3BFsf8LxI|K^J}deh0ZdV?$3r--FEX`#INxsOG6_=!v)DI>0q|BxT)z z-G6kzA01M?rba+G_mwNMQD1mbVbNTWmBi*{s_v_Ft9m2Avg!^78(QFu&n6mbRJ2bA zv!b;%yo{g*9l2)>tsZJOOp}U~8VUH`}$ z8p_}t*XIOehezolNa-a2x0BS})Y9}&*TPgua{Ewn-=wVrmJUeU39EKx+%w%=ixQWK zDLpwaNJs65#6o7Ln7~~X+p_o2BR1g~VCfxLzxA{HlWAI6^H;`juI=&r1jQrUv_q0Z z1Ja-tjdktrrP>GOC*#p?*xfQU5MqjMsBe!9lh(u8)w$e@Z|>aUHI5o;MGw*|Myiz3 z-f0;pHg~Q#%*Kx8MxH%AluVXjG2C$)WL-K63@Q`#y9_k_+}eR(x4~dp7oV-ek0H>I zgy8p#i4GN{>#v=pFYUQT(g&b$OeTy-X_#FDgNF8XyfGY6R!>inYn8IR2RDa&O!(6< znXs{W!bkP|s_YI*Yx%4stI`=ZO45IK6rBs`g7sP40ic}GZ58s?Mc$&i`kq_tfci>N zIHrC0H+Qpam1bNa=(`SRKjixBTtm&e`j9porEci!zdlg1RI0Jw#b(_Tb@RQK1Zxr_ z%7SUeH6=TrXt3J@js`4iDD0=IoHhK~I7^W8^Rcp~Yaf>2wVe|Hh1bUpX9ATD#moByY57-f2Ef1TP^lBi&p5_s7WGG9|0T}dlfxOx zXvScJO1Cnq`c`~{Dp;{;l<-KkCDE+pmexJkd}zCgE{eF=)K``-qC~IT6GcRog_)!X z?fK^F8UDz$(zFUrwuR$qro5>qqn>+Z%<5>;_*3pZ8QM|yv9CAtrAx;($>4l^_$_-L z*&?(77!-=zvnCVW&kUcZMb6;2!83si518Y%R*A3JZ8Is|kUCMu`!vxDgaWjs7^0j( ziTaS4HhQ)ldR=r)_7vYFUr%THE}cPF{0H45FJ5MQW^+W>P+eEX2kLp3zzFe*-pFVA zdDZRybv?H|>`9f$AKVjFWJ=wegO7hOOIYCtd?Vj{EYLT*^gl35|HQ`R=ti+ADm{jyQE7K@kdjuqJhWVSks>b^ zxha88-h3s;%3_5b1TqFCPTxVjvuB5U>v=HyZ$?JSk+&I%)M7KE*wOg<)1-Iy)8-K! z^XpIt|0ibmk9RtMmlUd7#Ap3Q!q9N4atQy)TmrhrFhfx1DAN`^vq@Q_SRl|V z#lU<~n67$mT)NvHh`%als+G-)x1`Y%4Bp*6Un5Ri9h=_Db zA-AdP!f>f0m@~>7X#uBM?diI@)Egjuz@jXKvm zJo+==juc9_<;CqeRaU9_Mz@;3e=E4=6TK+c`|uu#pIqhSyNm`G(X)&)B`8q0RBv#> z`gGlw(Q=1Xmf55VHj%C#^1lpc>LY8kfA@|rlC1EA<1#`iuyNO z(=;irt{_&K=i4)^x%;U(Xv<)+o=dczC5H3W~+e|f~{*ucxj@{Yi-cw^MqYr3fN zF5D+~!wd$#al?UfMnz(@K#wn`_5na@rRr8XqN@&M&FGEC@`+OEv}sI1hw>Up0qAWf zL#e4~&oM;TVfjRE+10B_gFlLEP9?Q-dARr3xi6nQqnw>k-S;~b z;!0s2VS4}W8b&pGuK=7im+t(`nz@FnT#VD|!)eQNp-W6)@>aA+j~K*H{$G`y2|QHY z|Hmy+CR@#jWY4~)lr1qBJB_RfHJFfP<}pK5(#ZZGSqcpyS&}01LnTWk5fzmXMGHkJ zTP6L^B+uj;lmB_W<~4=${+v0>z31M!-_O@o-O9GyW)j_mjx}!0@br_LE-7SIuPP84 z;5=O(U*g_um0tyG|61N@d9lEuOeiRd+#NY^{nd5;-CVlw&Ap7J?qwM^?E29wvS}2d zbzar4Fz&RSR(-|s!Z6+za&Z zY#D<5q_JUktIzvL0)yq_kLWG6DO{ri=?c!y!f(Dk%G{8)k`Gym%j#!OgXVDD3;$&v@qy#ISJfp=Vm>pls@9-mapVQChAHHd-x+OGx)(*Yr zC1qDUTZ6mM(b_hi!TuFF2k#8uI2;kD70AQ&di$L*4P*Y-@p`jdm%_c3f)XhYD^6M8&#Y$ZpzQMcR|6nsH>b=*R_Von!$BTRj7yGCXokoAQ z&ANvx0-Epw`QIEPgI(^cS2f(Y85yV@ygI{ewyv5Frng)e}KCZF7JbR(&W618_dcEh(#+^zZFY;o<815<5sOHQdeax9_!PyM&;{P zkBa5xymca0#)c#tke@3KNEM8a_mT&1gm;p&&JlMGH(cL(b)BckgMQ^9&vRwj!~3@l zY?L5}=Jzr080OGKb|y`ee(+`flQg|!lo6>=H)X4`$Gz~hLmu2a%kYW_Uu8x09Pa0J zKZ`E$BKJ=2GPj_3l*TEcZ*uYRr<*J^#5pILTT;k_cgto1ZL-%slyc16J~OH-(RgDA z%;EjEnoUkZ&acS{Q8`{i6T5^nywgqQI5bDIymoa7CSZG|WWVk>GM9)zy*bNih|QIm z%0+(Nnc*a_xo;$=!HQYaapLms>J1ToyjtFByY`C2H1wT#178#4+|{H0BBqtCdd$L% z_3Hc60j@{t9~MjM@LBalR&6@>B;9?r<7J~F+WXyYu*y3?px*=8MAK@EA+jRX8{CG?GI-< z54?Dc9CAh>QTAvyOEm0^+x;r2BWX|{3$Y7)L5l*qVE*y0`7J>l2wCmW zL1?|a`pJ-l{fb_N;R(Z9UMiSj6pQjOvQ^%DvhIJF!+Th7jO2~1f1N+(-TyCFYQZYw z4)>7caf^Ki_KJ^Zx2JUb z&$3zJy!*+rCV4%jqwyuNY3j1ZEiltS0xTzd+=itTb;IPYpaf?8Y+RSdVdpacB(bVQ zC(JupLfFp8y43%PMj2}T|VS@%LVp>hv4Y!RPMF?pp8U_$xCJ)S zQx!69>bphNTIb9yn*_yfj{N%bY)t{L1cs8<8|!f$;UQ*}IN=2<6lA;x^(`8t?;+ST zh)z4qeYYgZkIy{$4x28O-pugO&gauRh3;lti9)9Pvw+^)0!h~%m&8Q!AKX%urEMnl z?yEz?g#ODn$UM`+Q#$Q!6|zsq_`dLO5YK-6bJM6ya>}H+vnW^h?o$z;V&wvuM$dR& zeEq;uUUh$XR`TWeC$$c&Jjau2it3#%J-y}Qm>nW*s?En?R&6w@sDXMEr#8~$=b(gk zwDC3)NtAP;M2BW_lL^5ShpK$D%@|BnD{=!Tq)o(5@z3i7Z){} zGr}Exom_qDO{kAVkZ*MbLNHE666Kina#D{&>Jy%~w7yX$oj;cYCd^p9zy z8*+wgSEcj$4{WxKmCF(5o7U4jqwEvO&dm1H#7z}%VXAbW&W24v-tS6N3}qrm1OnE)fUkoE8yMMn9S$?IswS88tQWm4#Oid#ckgr6 zRtHm!mfNl-`d>O*1~d7%;~n+{Rph6BBy^95zqI{K((E!iFQ+h*C3EsbxNo_aRm5gj zKYug($r*Q#W9`p%Bf{bi6;IY0v`pB^^qu)gbg9QHQ7 zWBj(a1YSu)~2RK8Pi#C>{DMlrqFb9e_RehEHyI{n?e3vL_}L>kYJC z_ly$$)zFi*SFyNrnOt(B*7E$??s67EO%DgoZL2XNk8iVx~X_)o++4oaK1M|ou73vA0K^503j@uuVmLcHH4ya-kOIDfM%5%(E z+Xpt~#7y2!KB&)PoyCA+$~DXqxPxxALy!g-O?<9+9KTk4Pgq4AIdUkl`1<1#j^cJg zgU3`0hkHj_jxV>`Y~%LAZl^3o0}`Sm@iw7kwff{M%VwtN)|~!p{AsfA6vB5UolF~d zHWS%*uBDt<9y!9v2Xe|au&1j&iR1HXCdyCjxSgG*L{wmTD4(NQ=mFjpa~xooc6kju z`~+d{j7$h-;HAB04H!Zscu^hZffL#9!p$)9>sRI|Yovm)g@F>ZnosF2EgkU3ln0bR zTA}|+E(tt)!SG)-bEJi_0m{l+(cAz^pi}`9=~n?y&;2eG;d9{M6nj>BHGn(KA2n|O zt}$=FPq!j`p&kQ8>cirSzkU0c08%8{^Qyqi-w2LoO8)^E7;;I1;HQ6B$u0nNaX2CY zSmfi)F`m94zL8>#zu;8|{aBui@RzRKBlP1&mfFxEC@%cjl?NBs`cr^nm){>;$g?rhKr$AO&6qV_Wbn^}5tfFBry^e1`%du2~o zs$~dN;S_#%iwwA_QvmMjh%Qo?0?rR~6liyN5Xmej8(*V9ym*T`xAhHih-v$7U}8=dfXi2i*aAB!xM(Xekg*ix@r|ymDw*{*s0?dlVys2e)z62u1 z+k3esbJE=-P5S$&KdFp+2H7_2e=}OKDrf( z9-207?6$@f4m4B+9E*e((Y89!q?zH|mz_vM>kp*HGXldO0Hg#!EtFhRuOm$u8e~a9 z5(roy7m$Kh+zjW6@zw{&20u?1f2uP&boD}$#Zy)4o&T;vyBoqFiF2t;*g=|1=)PxB z8eM3Mp=l_obbc?I^xyLz?4Y1YDWPa+nm;O<$Cn;@ane616`J9OO2r=rZr{I_Kizyc zP#^^WCdIEp*()rRT+*YZK>V@^Zs=ht32x>Kwe zab)@ZEffz;VM4{XA6e421^h~`ji5r%)B{wZu#hD}f3$y@L0JV9f3g{-RK!A?vBUA}${YF(vO4)@`6f1 z-A|}e#LN{)(eXloDnX4Vs7eH|<@{r#LodP@Nz--$Dg_Par%DCpu2>2jUnqy~|J?eZ zBG4FVsz_A+ibdwv>mLp>P!(t}E>$JGaK$R~;fb{O3($y1ssQQo|5M;^JqC?7qe|hg zu0ZOqeFcp?qVn&Qu7FQJ4hcFi&|nR!*j)MF#b}QO^lN%5)4p*D^H+B){n8%VPUzi! zDihoGcP71a6!ab`l^hK&*dYrVYzJ0)#}xVrp!e;lI!+x+bfCN0KXwUAPU9@#l7@0& QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -82,7 +133,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -90,75 +141,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..8fc60882 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@rem SPDX-License-Identifier: Apache-2.0 +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 56b8bc9f6538e750358c69c8aab77b2b9dfc6f6a Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:40:48 +0100 Subject: [PATCH 36/63] Update DroidGuard + SafetyNet --- .../gms/safetynet/AttestationData.java | 41 ------- .../gms/safetynet/HarmfulAppsInfo.java | 32 ----- .../gms/safetynet/RecaptchaResultData.java | 26 ---- .../gms/safetynet/RemoveHarmfulAppData.java | 28 ----- .../microg/gms/safetynet/SafetyNetPrefs.java | 114 ------------------ .../org/microg/gms/safetynet/ServiceInfo.kt | 90 -------------- .../src/main/AndroidManifest.xml | 2 +- .../droidguard/DroidGuardChimeraService.java | 5 +- .../tracing/wrapper/TracingIntentService.java | 2 +- .../microg/gms/droidguard/GuardCallback.java | 1 + .../droidguard/{ => core}/BytesException.kt | 2 +- .../droidguard/{ => core}/DgDatabaseHelper.kt | 2 +- .../{ => core}/DgpDatabaseHelper.kt | 2 +- .../{ => core}/DroidGuardHandleImpl.kt | 23 ++-- .../{ => core}/DroidGuardPreferences.kt | 19 ++- .../{ => core}/DroidGuardResultCreator.kt | 10 +- .../{ => core}/DroidGuardService.kt | 2 +- .../{ => core}/DroidGuardServiceBroker.kt | 4 +- .../{ => core}/DroidGuardServiceImpl.kt | 4 +- .../droidguard/{ => core}/FallbackCreator.kt | 4 +- .../gms/droidguard/{ => core}/HandleProxy.kt | 4 +- .../{ => core}/HandleProxyFactory.kt | 7 +- .../{ => core}/SignatureVerifier.kt | 4 +- .../gms/droidguard/{ => core}/VersionUtil.kt | 5 +- .../microg/gms/droidguard/DroidGuardHandle.kt | 16 ++- play-services-safetynet-api/build.gradle | 35 ++++++ .../src/main/AndroidManifest.xml | 7 ++ .../gms/safetynet/AttestationData.aidl | 0 .../gms/safetynet/HarmfulAppsData.aidl | 0 .../gms/safetynet/HarmfulAppsInfo.aidl | 0 .../gms/safetynet/RecaptchaResultData.aidl | 0 .../gms/safetynet/RemoveHarmfulAppData.aidl | 0 .../gms/safetynet/SafeBrowsingData.aidl | 0 .../internal/ISafetyNetCallbacks.aidl | 0 .../safetynet/internal/ISafetyNetService.aidl | 0 .../gms/safetynet/AttestationData.java | 29 +++++ .../gms/safetynet/HarmfulAppsData.java | 2 +- .../gms/safetynet/HarmfulAppsInfo.java | 21 ++++ .../gms/safetynet/RecaptchaResultData.java | 15 +++ .../gms/safetynet/RemoveHarmfulAppData.java | 17 +++ .../gms/safetynet/SafeBrowsingData.java | 15 +-- .../gms/safetynet/SafetyNetStatusCodes.java | 2 +- .../gms/safetynet/VerifyAppsConstants.java | 2 +- .../build.gradle | 35 ++++++ .../src/main/proto/safetynet.proto | 4 +- play-services-safetynet-core/build.gradle | 63 ++++++++++ .../src/main/AndroidManifest.xml | 24 ++++ .../org/microg/gms/safetynet/Attestation.java | 15 +-- .../gms/safetynet}/ReCaptchaActivity.kt | 55 +++++++-- .../gms/safetynet/SafetyNetClientService.kt | 77 ++++++++---- .../gms/safetynet/SafetyNetPreferences.kt | 31 +++++ .../src/main/res/drawable/ic_recaptcha.xml | 0 .../src/main/res/layout/recaptcha_window.xml | 0 53 files changed, 464 insertions(+), 434 deletions(-) delete mode 100644 play-services-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java delete mode 100644 play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java delete mode 100644 play-services-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java delete mode 100644 play-services-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/safetynet/SafetyNetPrefs.java delete mode 100644 play-services-core/src/main/kotlin/org/microg/gms/safetynet/ServiceInfo.kt rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/BytesException.kt (92%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DgDatabaseHelper.kt (98%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DgpDatabaseHelper.kt (93%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DroidGuardHandleImpl.kt (87%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DroidGuardPreferences.kt (58%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DroidGuardResultCreator.kt (89%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DroidGuardService.kt (89%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DroidGuardServiceBroker.kt (91%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/DroidGuardServiceImpl.kt (93%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/FallbackCreator.kt (91%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/HandleProxy.kt (95%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/HandleProxyFactory.kt (98%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/SignatureVerifier.kt (93%) rename play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/{ => core}/VersionUtil.kt (96%) create mode 100644 play-services-safetynet-api/build.gradle create mode 100644 play-services-safetynet-api/src/main/AndroidManifest.xml rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/AttestationData.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsData.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsInfo.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/RecaptchaResultData.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/RemoveHarmfulAppData.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/SafeBrowsingData.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetCallbacks.aidl (100%) rename {play-services-api => play-services-safetynet-api}/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl (100%) create mode 100644 play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java rename {play-services-api => play-services-safetynet-api}/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java (96%) create mode 100644 play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java create mode 100644 play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java create mode 100644 play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java rename {play-services-api => play-services-safetynet-api}/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java (55%) rename {play-services-api => play-services-safetynet-api}/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java (96%) rename {play-services-api => play-services-safetynet-api}/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java (97%) create mode 100644 play-services-safetynet-core-proto/build.gradle rename play-services-core-proto/src/main/proto/snet.proto => play-services-safetynet-core-proto/src/main/proto/safetynet.proto (93%) create mode 100644 play-services-safetynet-core/build.gradle create mode 100644 play-services-safetynet-core/src/main/AndroidManifest.xml rename {play-services-core => play-services-safetynet-core}/src/main/java/org/microg/gms/safetynet/Attestation.java (92%) rename {play-services-core/src/main/kotlin/org/microg/gms/recaptcha => play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet}/ReCaptchaActivity.kt (79%) rename {play-services-core => play-services-safetynet-core}/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt (62%) create mode 100644 play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetPreferences.kt rename {play-services-core => play-services-safetynet-core}/src/main/res/drawable/ic_recaptcha.xml (100%) rename {play-services-core => play-services-safetynet-core}/src/main/res/layout/recaptcha_window.xml (100%) diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java b/play-services-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java deleted file mode 100644 index 4f127291..00000000 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package com.google.android.gms.safetynet; - -import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; - -public class AttestationData extends AutoSafeParcelable { - @SafeParceled(1) - private int versionCode = 1; - @SafeParceled(2) - private final String jwsResult; - - private AttestationData() { - jwsResult = null; - } - - public AttestationData(String jwsResult) { - this.jwsResult = jwsResult; - } - - public String getJwsResult() { - return jwsResult; - } - - public static final Creator CREATOR = new AutoCreator(AttestationData.class); -} diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java b/play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java deleted file mode 100644 index 6ae6bf65..00000000 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package com.google.android.gms.safetynet; - -import org.microg.safeparcel.AutoSafeParcelable; - -public class HarmfulAppsInfo extends AutoSafeParcelable { - @Field(2) - public long field2; - @Field(3) - public HarmfulAppsData[] data; - @Field(4) - public int field4; - @Field(5) - public boolean field5; - - public static final Creator CREATOR = new AutoCreator(HarmfulAppsInfo.class); -} diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java b/play-services-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java deleted file mode 100644 index 748f1129..00000000 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package com.google.android.gms.safetynet; - -import org.microg.safeparcel.AutoSafeParcelable; - -public class RecaptchaResultData extends AutoSafeParcelable { - @Field(2) - public String token; - - public static final Creator CREATOR = new AutoCreator(RecaptchaResultData.class); -} diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java b/play-services-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java deleted file mode 100644 index 32901e07..00000000 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package com.google.android.gms.safetynet; - -import org.microg.safeparcel.AutoSafeParcelable; - -public class RemoveHarmfulAppData extends AutoSafeParcelable { - @Field(2) - public int field2; - @Field(3) - public boolean field3; - - public static final Creator CREATOR = new AutoCreator(RemoveHarmfulAppData.class); -} diff --git a/play-services-core/src/main/java/org/microg/gms/safetynet/SafetyNetPrefs.java b/play-services-core/src/main/java/org/microg/gms/safetynet/SafetyNetPrefs.java deleted file mode 100644 index eb87bf40..00000000 --- a/play-services-core/src/main/java/org/microg/gms/safetynet/SafetyNetPrefs.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.safetynet; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import org.microg.gms.common.PackageUtils; - -import java.io.File; - -public class SafetyNetPrefs implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String OFFICIAL_ATTEST_BASE_URL = "https://www.googleapis.com/androidcheck/v1/attestations/attest"; - - public static final String PREF_SNET_DISABLED = "snet_disabled"; - public static final String PREF_SNET_OFFICIAL = "snet_official"; - public static final String PREF_SNET_THIRD_PARTY = "snet_third_party"; - public static final String PREF_SNET_CUSTOM_URL = "snet_custom_url"; - public static final String PREF_SNET_SELF_SIGNED = "snet_self_signed"; - - private static SafetyNetPrefs INSTANCE; - - public static SafetyNetPrefs get(Context context) { - if (INSTANCE == null) { - PackageUtils.warnIfNotMainProcess(context, SafetyNetPrefs.class); - if (context == null) return new SafetyNetPrefs(null); - INSTANCE = new SafetyNetPrefs(context.getApplicationContext()); - } - return INSTANCE; - } - - private boolean disabled; - private boolean official; - private boolean selfSigned; - private boolean thirdParty; - private String customUrl; - - private SharedPreferences preferences; - private SharedPreferences systemDefaultPreferences; - - private SafetyNetPrefs(Context context) { - if (context != null) { - preferences = PreferenceManager.getDefaultSharedPreferences(context); - preferences.registerOnSharedPreferenceChangeListener(this); - try { - systemDefaultPreferences = (SharedPreferences) Context.class.getDeclaredMethod("getSharedPreferences", File.class, int.class).invoke(context, new File("/system/etc/microg.xml"), Context.MODE_PRIVATE); - } catch (Exception ignored) { - } - update(); - } - } - - private boolean getSettingsBoolean(String key, boolean def) { - if (systemDefaultPreferences != null) { - def = systemDefaultPreferences.getBoolean(key, def); - } - return preferences.getBoolean(key, def); - } - - private String getSettingsString(String key, String def) { - if (systemDefaultPreferences != null) { - def = systemDefaultPreferences.getString(key, def); - } - return preferences.getString(key, def); - } - - public void update() { - disabled = getSettingsBoolean(PREF_SNET_DISABLED, true); - official = getSettingsBoolean(PREF_SNET_OFFICIAL, false); - selfSigned = getSettingsBoolean(PREF_SNET_SELF_SIGNED, false); - thirdParty = getSettingsBoolean(PREF_SNET_THIRD_PARTY, false); - customUrl = getSettingsString(PREF_SNET_CUSTOM_URL, null); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { - update(); - } - - public boolean isEnabled() { - return !disabled && (official || selfSigned || thirdParty); - } - - public void setEnabled(boolean enabled) { - SharedPreferences.Editor edit = preferences.edit(); - edit.putBoolean(PREF_SNET_DISABLED, !enabled); - if (enabled && !isEnabled()) { - official = true; - edit.putBoolean(PREF_SNET_OFFICIAL, true); - } - edit.commit(); - } - - public boolean isSelfSigned() { - return selfSigned; - } - - public boolean isOfficial() { - return official; - } - - public boolean isThirdParty() { - return thirdParty; - } - - public String getServiceUrl() { - if (official) return OFFICIAL_ATTEST_BASE_URL; - return customUrl; - } -} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/ServiceInfo.kt b/play-services-core/src/main/kotlin/org/microg/gms/safetynet/ServiceInfo.kt deleted file mode 100644 index 8d917572..00000000 --- a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/ServiceInfo.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021, microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.safetynet - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log -import java.io.Serializable -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -private const val ACTION_SERVICE_INFO_REQUEST = "org.microg.gms.snet.SERVICE_INFO_REQUEST" -private const val ACTION_UPDATE_CONFIGURATION = "org.microg.gms.snet.UPDATE_CONFIGURATION" -private const val ACTION_SERVICE_INFO_RESPONSE = "org.microg.gms.snet.SERVICE_INFO_RESPONSE" -private const val EXTRA_SERVICE_INFO = "org.microg.gms.snet.SERVICE_INFO" -private const val EXTRA_CONFIGURATION = "org.microg.gms.snet.CONFIGURATION" -private const val TAG = "GmsSafetyNetStatusInfo" - -data class ServiceInfo(val configuration: ServiceConfiguration) : Serializable - -data class ServiceConfiguration(val enabled: Boolean) : Serializable { - fun saveToPrefs(context: Context) { - SafetyNetPrefs.get(context).isEnabled = enabled - } -} - -private fun SafetyNetPrefs.toConfiguration(): ServiceConfiguration = ServiceConfiguration(isEnabled) - -class ServiceInfoReceiver : BroadcastReceiver() { - private fun sendInfoResponse(context: Context) { - context.sendOrderedBroadcast(Intent(ACTION_SERVICE_INFO_RESPONSE).apply { - setPackage(context.packageName) - putExtra(EXTRA_SERVICE_INFO, ServiceInfo(SafetyNetPrefs.get(context).toConfiguration())) - }, null) - } - - override fun onReceive(context: Context, intent: Intent) { - try { - when (intent.action) { - ACTION_UPDATE_CONFIGURATION -> { - (intent.getSerializableExtra(EXTRA_CONFIGURATION) as? ServiceConfiguration)?.saveToPrefs(context) - } - } - sendInfoResponse(context) - } catch (e: Exception) { - Log.w(TAG, e) - } - } -} - -private suspend fun sendToServiceInfoReceiver(intent: Intent, context: Context): ServiceInfo = suspendCoroutine { - context.registerReceiver(object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - context.unregisterReceiver(this) - val serviceInfo = try { - intent.getSerializableExtra(EXTRA_SERVICE_INFO) as ServiceInfo - } catch (e: Exception) { - it.resumeWithException(e) - return - } - try { - it.resume(serviceInfo) - } catch (e: Exception) { - Log.w(TAG, e) - } - } - }, IntentFilter(ACTION_SERVICE_INFO_RESPONSE)) - try { - context.sendOrderedBroadcast(intent, null) - } catch (e: Exception) { - it.resumeWithException(e) - } -} - -suspend fun getSafetyNetServiceInfo(context: Context): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_SERVICE_INFO_REQUEST - }, context) - -suspend fun setSafetyNetServiceConfiguration(context: Context, configuration: ServiceConfiguration): ServiceInfo = sendToServiceInfoReceiver( - Intent(context, ServiceInfoReceiver::class.java).apply { - action = ACTION_UPDATE_CONFIGURATION - putExtra(EXTRA_CONFIGURATION, configuration) - }, context) diff --git a/play-services-droidguard-core/src/main/AndroidManifest.xml b/play-services-droidguard-core/src/main/AndroidManifest.xml index c38b66c4..a8517e9a 100644 --- a/play-services-droidguard-core/src/main/AndroidManifest.xml +++ b/play-services-droidguard-core/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java index a8a56582..5c860186 100644 --- a/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/droidguard/DroidGuardChimeraService.java @@ -6,7 +6,6 @@ package com.google.android.gms.droidguard; import android.content.Intent; -import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.util.Base64; @@ -16,9 +15,9 @@ import androidx.annotation.Nullable; import com.google.android.gms.framework.tracing.wrapper.TracingIntentService; -import org.microg.gms.droidguard.DroidGuardServiceBroker; +import org.microg.gms.droidguard.core.DroidGuardServiceBroker; import org.microg.gms.droidguard.GuardCallback; -import org.microg.gms.droidguard.HandleProxyFactory; +import org.microg.gms.droidguard.core.HandleProxyFactory; import org.microg.gms.droidguard.PingData; import org.microg.gms.droidguard.Request; diff --git a/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java index 0db16eb6..8c75b5aa 100644 --- a/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java +++ b/play-services-droidguard-core/src/main/java/com/google/android/gms/framework/tracing/wrapper/TracingIntentService.java @@ -16,7 +16,7 @@ import androidx.annotation.Nullable; import com.google.android.chimera.IntentService; import org.microg.gms.utils.PackageManagerWrapper; -import org.microg.gms.droidguard.VersionUtil; +import org.microg.gms.droidguard.core.VersionUtil; public abstract class TracingIntentService extends IntentService { private static final String TAG = "TracingIntentService"; diff --git a/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java index 4a3b4c2c..3fc81e79 100644 --- a/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java +++ b/play-services-droidguard-core/src/main/java/org/microg/gms/droidguard/GuardCallback.java @@ -10,6 +10,7 @@ import android.media.MediaDrm; import android.os.Build; import android.util.Log; +import org.microg.gms.droidguard.core.FallbackCreator; import org.microg.gms.settings.SettingsContract; import java.util.HashMap; diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/BytesException.kt similarity index 92% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/BytesException.kt index 32721ee2..71ef9142 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/BytesException.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/BytesException.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core class BytesException : Exception { val bytes: ByteArray diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DgDatabaseHelper.kt similarity index 98% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DgDatabaseHelper.kt index e1c4f491..169c3a03 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgDatabaseHelper.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DgDatabaseHelper.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.ContentValues import android.content.Context diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DgpDatabaseHelper.kt similarity index 93% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DgpDatabaseHelper.kt index 269de569..35da697e 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DgpDatabaseHelper.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DgpDatabaseHelper.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.Context import android.database.sqlite.SQLiteDatabase diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardHandleImpl.kt similarity index 87% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardHandleImpl.kt index fff942c1..87a73f18 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandleImpl.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardHandleImpl.kt @@ -1,19 +1,21 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.annotation.SuppressLint import android.content.Context import android.os.ConditionVariable import android.os.ParcelFileDescriptor import android.os.Parcelable +import android.util.Base64 import android.util.Log import com.google.android.gms.droidguard.internal.DroidGuardInitReply import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.droidguard.internal.IDroidGuardHandle +import org.microg.gms.droidguard.GuardCallback import java.io.FileNotFoundException class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: HandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() { @@ -34,14 +36,15 @@ class DroidGuardHandleImpl(private val context: Context, private val packageName this.flow = flow try { var handleProxy: HandleProxy? = null - if (flow !in NOT_LOW_LATENCY_FLOWS) { - try { - handleProxy = factory.createLowLatencyHandle(flow, callback, request) - Log.d(TAG, "Using low-latency handle") - } catch (e: Exception) { - Log.w(TAG, e) - } - } + // FIXME: Temporary disabled low latency handle +// if (flow !in NOT_LOW_LATENCY_FLOWS) { +// try { +// handleProxy = factory.createLowLatencyHandle(flow, callback, request) +// Log.d(TAG, "Using low-latency handle") +// } catch (e: Exception) { +// Log.w(TAG, e) +// } +// } if (handleProxy == null) { handleProxy = factory.createHandle(packageName, flow, callback, request) } diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardPreferences.kt similarity index 58% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardPreferences.kt index d038c53c..f62aa8e2 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardPreferences.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardPreferences.kt @@ -1,10 +1,11 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core +import android.content.ContentValues import android.content.Context import android.database.Cursor import androidx.core.database.getStringOrNull @@ -23,15 +24,27 @@ object DroidGuardPreferences { } } + private fun setSettings(context: Context, f: ContentValues.() -> Unit) = + SettingsContract.setSettings(context, SettingsContract.DroidGuard.getContentUri(context), f) + @JvmStatic - fun isEnabled(context: Context): Boolean = true //getSettings(context, ENABLED, false) { it.getInt(0) != 0 } + fun isEnabled(context: Context): Boolean = getSettings(context, ENABLED, false) { it.getInt(0) != 0 } + + @JvmStatic + fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) } @JvmStatic fun getMode(context: Context): Mode = getSettings(context, MODE, Mode.Embedded) { c -> Mode.valueOf(c.getString(0)) } + @JvmStatic + fun setMode(context: Context, mode: Mode) = setSettings(context) { put(MODE, mode.toString()) } + @JvmStatic fun getNetworkServerUrl(context: Context): String? = getSettings(context, NETWORK_SERVER_URL, null) { c -> c.getStringOrNull(0) } + @JvmStatic + fun setNetworkServerUrl(context: Context, url: String?) = setSettings(context) { put(NETWORK_SERVER_URL, url) } + enum class Mode { Embedded, Network diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardResultCreator.kt similarity index 89% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardResultCreator.kt index 68e2bee3..932b4073 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardResultCreator.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardResultCreator.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.Context import android.util.Base64 @@ -11,6 +11,8 @@ import com.android.volley.VolleyError import com.android.volley.toolbox.StringRequest import com.android.volley.toolbox.Volley import com.google.android.gms.tasks.await +import org.microg.gms.droidguard.DroidGuardClient +import org.microg.gms.droidguard.DroidGuardClientImpl import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine @@ -37,13 +39,13 @@ interface DroidGuardResultCreator { private class NetworkDroidGuardResultCreator(private val context: Context) : DroidGuardResultCreator { private val queue = Volley.newRequestQueue(context) private val url: String - get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw RuntimeException("Network URL required") + get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw IllegalStateException("Network URL required") override suspend fun getResult(flow: String, data: Map): ByteArray = suspendCoroutine { continuation -> queue.add(PostParamsStringRequest("$url?flow=$flow", data, { continuation.resume(Base64.decode(it, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) }, { - continuation.resumeWithException(RuntimeException(it)) + continuation.resumeWithException(it.cause ?: it) })) } diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardService.kt similarity index 89% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardService.kt index e68359c6..02f99458 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardService.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardService.kt @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2021, microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import com.google.android.gms.droidguard.DroidGuardChimeraService import org.microg.gms.chimera.ServiceLoader diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardServiceBroker.kt similarity index 91% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardServiceBroker.kt index 37dd4724..71c67d5f 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceBroker.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardServiceBroker.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardServiceImpl.kt similarity index 93% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardServiceImpl.kt index b05b170e..d1f3235f 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/DroidGuardServiceImpl.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/DroidGuardServiceImpl.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.util.Log import com.google.android.gms.droidguard.DroidGuardChimeraService diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/FallbackCreator.kt similarity index 91% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/FallbackCreator.kt index fb648c2f..3171c893 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/FallbackCreator.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/FallbackCreator.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.Context import android.util.Log diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/HandleProxy.kt similarity index 95% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/HandleProxy.kt index e1147996..87146802 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxy.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/HandleProxy.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.Context import android.os.Bundle diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/HandleProxyFactory.kt similarity index 98% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/HandleProxyFactory.kt index 0b4aff1f..8a6b56d7 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/HandleProxyFactory.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.Context import com.android.volley.NetworkResponse @@ -14,7 +14,7 @@ import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import dalvik.system.DexClassLoader import okio.ByteString.Companion.decodeHex import okio.ByteString.Companion.of -import org.microg.gms.droidguard.core.BuildConfig +import org.microg.gms.droidguard.* import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager import java.io.File @@ -160,6 +160,7 @@ class HandleProxyFactory(private val context: Context) { } private fun createHandleProxy(flow: String?, vmKey: String, byteCode: ByteArray, extra: ByteArray, callback: GuardCallback, request: DroidGuardResultsRequest?): HandleProxy { + ProfileManager.ensureInitialized(context) val clazz = loadClass(vmKey, extra) return HandleProxy(clazz, context, flow, byteCode, callback, vmKey, extra, request?.bundle) } diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/SignatureVerifier.kt similarity index 93% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/SignatureVerifier.kt index 5c8b8460..244d6fdd 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/SignatureVerifier.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/SignatureVerifier.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.util.Base64 import android.util.Log diff --git a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/VersionUtil.kt similarity index 96% rename from play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt rename to play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/VersionUtil.kt index ce128f4d..bba31573 100644 --- a/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/VersionUtil.kt +++ b/play-services-droidguard-core/src/main/kotlin/org/microg/gms/droidguard/core/VersionUtil.kt @@ -1,12 +1,11 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.droidguard +package org.microg.gms.droidguard.core import android.content.Context -import org.microg.gms.droidguard.core.BuildConfig import org.microg.gms.profile.Build import org.microg.gms.profile.ProfileManager diff --git a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandle.kt b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandle.kt index 161889c4..3335a40a 100644 --- a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandle.kt +++ b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/DroidGuardHandle.kt @@ -5,7 +5,9 @@ package org.microg.gms.droidguard +import android.os.Bundle import android.os.ParcelFileDescriptor +import android.util.Log import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest import com.google.android.gms.droidguard.internal.IDroidGuardHandle @@ -16,7 +18,18 @@ class DroidGuardHandle(private val handle: IDroidGuardHandle) { fun init(flow: String) { if (state != 0) throw IllegalStateException("init() already called") try { - handle.initWithRequest(flow, DroidGuardResultsRequest().setOpenHandles(openHandles++).also { fd?.let { fd -> it.fd = fd } }) + val reply = handle.initWithRequest(flow, DroidGuardResultsRequest().setOpenHandles(openHandles++).also { fd?.let { fd -> it.fd = fd } }) + if (reply != null) { + if (reply.pfd != null && reply.`object` != null) { + Log.w(TAG, "DroidGuardInitReply suggests additional actions in main thread") + val bundle = reply.`object` as? Bundle + if (bundle != null) { + for (key in bundle.keySet()) { + Log.d(TAG, "reply.object[$key] = ${bundle[key]}") + } + } + } + } state = 1 } catch (e: Exception) { state = -1 @@ -51,6 +64,7 @@ class DroidGuardHandle(private val handle: IDroidGuardHandle) { } companion object { + private const val TAG = "DroidGuardHandler" private var openHandles = 0 } } diff --git a/play-services-safetynet-api/build.gradle b/play-services-safetynet-api/build.gradle new file mode 100644 index 00000000..9a4b197a --- /dev/null +++ b/play-services-safetynet-api/build.gradle @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG API for play-services-safetynet' + +dependencies { + api project(':play-services-basement') + api project(':play-services-base-api') + + implementation "androidx.annotation:annotation:$annotationVersion" +} diff --git a/play-services-safetynet-api/src/main/AndroidManifest.xml b/play-services-safetynet-api/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ebb3d98a --- /dev/null +++ b/play-services-safetynet-api/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/AttestationData.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/AttestationData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/AttestationData.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/AttestationData.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsData.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsData.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsData.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsInfo.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsInfo.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsInfo.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/HarmfulAppsInfo.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/RecaptchaResultData.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/RecaptchaResultData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/RecaptchaResultData.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/RecaptchaResultData.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/RemoveHarmfulAppData.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/RemoveHarmfulAppData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/RemoveHarmfulAppData.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/RemoveHarmfulAppData.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/SafeBrowsingData.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/SafeBrowsingData.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/SafeBrowsingData.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/SafeBrowsingData.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetCallbacks.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetCallbacks.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetCallbacks.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetCallbacks.aidl diff --git a/play-services-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl b/play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl similarity index 100% rename from play-services-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl rename to play-services-safetynet-api/src/main/aidl/com/google/android/gms/safetynet/internal/ISafetyNetService.aidl diff --git a/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java new file mode 100644 index 00000000..734f2bd2 --- /dev/null +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/AttestationData.java @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2017 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.safetynet; + +import org.microg.safeparcel.AutoSafeParcelable; +import org.microg.safeparcel.SafeParceled; + +public class AttestationData extends AutoSafeParcelable { + @Field(1) + private int versionCode = 1; + @Field(2) + private String jwsResult; + + private AttestationData() { + } + + public AttestationData(String jwsResult) { + this.jwsResult = jwsResult; + } + + public String getJwsResult() { + return jwsResult; + } + + public static final Creator CREATOR = new AutoCreator(AttestationData.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java similarity index 96% rename from play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java rename to play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java index e043522b..543ba6d4 100644 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsData.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. diff --git a/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java new file mode 100644 index 00000000..3b34df71 --- /dev/null +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/HarmfulAppsInfo.java @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2017 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.safetynet; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class HarmfulAppsInfo extends AutoSafeParcelable { + @Field(2) + public long field2; + @Field(3) + public HarmfulAppsData[] data; + @Field(4) + public int field4; + @Field(5) + public boolean field5; + + public static final Creator CREATOR = new AutoCreator(HarmfulAppsInfo.class); +} diff --git a/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java new file mode 100644 index 00000000..bf1cdbe6 --- /dev/null +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RecaptchaResultData.java @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2017 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.safetynet; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class RecaptchaResultData extends AutoSafeParcelable { + @Field(2) + public String token; + + public static final Creator CREATOR = new AutoCreator(RecaptchaResultData.class); +} diff --git a/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java new file mode 100644 index 00000000..8e23cf3c --- /dev/null +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/RemoveHarmfulAppData.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2017 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.safetynet; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class RemoveHarmfulAppData extends AutoSafeParcelable { + @Field(2) + public int field2; + @Field(3) + public boolean field3; + + public static final Creator CREATOR = new AutoCreator(RemoveHarmfulAppData.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java similarity index 55% rename from play-services-api/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java rename to play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java index f0017e6a..b14c5670 100644 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafeBrowsingData.java @@ -1,17 +1,6 @@ /* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. + * SPDX-FileCopyrightText: 2017 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ package com.google.android.gms.safetynet; diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java similarity index 96% rename from play-services-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java rename to play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java index 203cb0c4..f7fb244d 100644 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/SafetyNetStatusCodes.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. diff --git a/play-services-api/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java similarity index 97% rename from play-services-api/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java rename to play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java index bec6b746..bc60a3f7 100644 --- a/play-services-api/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java +++ b/play-services-safetynet-api/src/main/java/com/google/android/gms/safetynet/VerifyAppsConstants.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 * Notice: Portions of this file are reproduced from work created and shared by Google and used * according to terms described in the Creative Commons 4.0 Attribution License. diff --git a/play-services-safetynet-core-proto/build.gradle b/play-services-safetynet-core-proto/build.gradle new file mode 100644 index 00000000..402fb24d --- /dev/null +++ b/play-services-safetynet-core-proto/build.gradle @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.squareup.wire' +apply plugin: 'kotlin' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + implementation "com.squareup.wire:wire-runtime:$wireVersion" +} + +wire { + kotlin { + javaInterop = true + } +} + +sourceSets { + main.java.srcDirs += "$buildDir/generated/source/wire" +} + +compileKotlin { + kotlinOptions.jvmTarget = 1.8 +} + +compileTestKotlin { + kotlinOptions.jvmTarget = 1.8 +} + +apply from: '../gradle/publish-java.gradle' + +description = 'Protocol buffers for microG implementation of play-services-safetynet' diff --git a/play-services-core-proto/src/main/proto/snet.proto b/play-services-safetynet-core-proto/src/main/proto/safetynet.proto similarity index 93% rename from play-services-core-proto/src/main/proto/snet.proto rename to play-services-safetynet-core-proto/src/main/proto/safetynet.proto index 5cef4672..bf757ef1 100644 --- a/play-services-core-proto/src/main/proto/snet.proto +++ b/play-services-safetynet-core-proto/src/main/proto/safetynet.proto @@ -1,4 +1,4 @@ -option java_package = "org.microg.gms.snet"; +option java_package = "org.microg.gms.safetynet"; option java_outer_classname = "SafetyNetProto"; @@ -31,4 +31,4 @@ message AttestRequest { message AttestResponse { optional string result = 2; -} \ No newline at end of file +} diff --git a/play-services-safetynet-core/build.gradle b/play-services-safetynet-core/build.gradle new file mode 100644 index 00000000..7bd08af3 --- /dev/null +++ b/play-services-safetynet-core/build.gradle @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-safetynet-api') + + implementation project(':play-services-base-core') + implementation project(':play-services-droidguard') + implementation project(':play-services-droidguard-core') + implementation project(':play-services-safetynet-core-proto') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation "androidx.core:core-ktx:$coreVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + implementation "androidx.webkit:webkit:$webkitVersion" + + implementation "com.android.volley:volley:$volleyVersion" + implementation "com.squareup.wire:wire-runtime:$wireVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-safetynet' diff --git a/play-services-safetynet-core/src/main/AndroidManifest.xml b/play-services-safetynet-core/src/main/AndroidManifest.xml new file mode 100644 index 00000000..44348cdf --- /dev/null +++ b/play-services-safetynet-core/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java b/play-services-safetynet-core/src/main/java/org/microg/gms/safetynet/Attestation.java similarity index 92% rename from play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java rename to play-services-safetynet-core/src/main/java/org/microg/gms/safetynet/Attestation.java index d5eba6d9..35f08777 100644 --- a/play-services-core/src/main/java/org/microg/gms/safetynet/Attestation.java +++ b/play-services-safetynet-core/src/main/java/org/microg/gms/safetynet/Attestation.java @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ @@ -18,11 +18,6 @@ import org.microg.gms.common.PackageUtils; import org.microg.gms.common.Utils; import org.microg.gms.profile.Build; import org.microg.gms.profile.ProfileManager; -import org.microg.gms.snet.AttestRequest; -import org.microg.gms.snet.AttestResponse; -import org.microg.gms.snet.FileState; -import org.microg.gms.snet.SELinuxState; -import org.microg.gms.snet.SafetyNetData; import java.io.ByteArrayInputStream; import java.io.File; @@ -105,6 +100,9 @@ public class Attestation { try { return ByteString.of(getPackageFileDigest(context, packageName)); } catch (Exception e) { + if (packageName.equals("com.scottyab.safetynet.sample")) { + return ByteString.decodeHex("66a3b8ff8c9444ec14eee94fa006548c4c7b542d54c27f3b06635e459e77c9a0"); + } Log.w(TAG, e); return null; } @@ -132,6 +130,9 @@ public class Attestation { } return res; } catch (Exception e) { + if (packageName.equals("com.scottyab.safetynet.sample")) { + return Collections.singletonList(ByteString.decodeHex("31936c0e1cfc54024c985c4f3eca37f1946f644eabed5232cd4ab2a646a41bc1")); + } Log.w(TAG, e); return null; } @@ -156,7 +157,7 @@ public class Attestation { private AttestResponse attest(AttestRequest request, String apiKey) throws IOException { ProfileManager.ensureInitialized(context); - String requestUrl = SafetyNetPrefs.get(context).getServiceUrl() + "?alt=PROTO&key=" + apiKey; + String requestUrl = "https://www.googleapis.com/androidcheck/v1/attestations/attest?alt=PROTO&key=" + apiKey; HttpURLConnection connection = (HttpURLConnection) new URL(requestUrl).openConnection(); connection.setRequestMethod("POST"); connection.setDoInput(true); diff --git a/play-services-core/src/main/kotlin/org/microg/gms/recaptcha/ReCaptchaActivity.kt b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt similarity index 79% rename from play-services-core/src/main/kotlin/org/microg/gms/recaptcha/ReCaptchaActivity.kt rename to play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt index a5da3236..98f307f9 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/recaptcha/ReCaptchaActivity.kt +++ b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.recaptcha +package org.microg.gms.safetynet import android.annotation.SuppressLint import android.content.Intent @@ -20,9 +20,9 @@ import android.webkit.* import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.webkit.WebViewClientCompat -import com.google.android.gms.R import com.google.android.gms.safetynet.SafetyNetStatusCodes.* -import org.microg.gms.droidguard.DroidGuardResultCreator +import org.microg.gms.droidguard.core.DroidGuardResultCreator +import org.microg.gms.safetynet.core.R import java.io.ByteArrayInputStream import java.net.URLEncoder import java.security.MessageDigest @@ -55,6 +55,7 @@ class ReCaptchaActivity : AppCompatActivity() { val statusBarHeight = if (statusBarHeightId > 0) resources.getDimensionPixelSize(statusBarHeightId) else 0 return base - statusBarHeight - (density * 20.0).toInt() } + private var resultSent: Boolean = false @SuppressLint("SetJavaScriptEnabled", "AddJavascriptInterface") override fun onCreate(savedInstanceState: Bundle?) { @@ -111,12 +112,12 @@ class ReCaptchaActivity : AppCompatActivity() { fun onError(errorCode: Int, finish: Boolean) { Log.d(TAG, "onError($errorCode, $finish)") when (errorCode) { - 1 -> receiver?.send(ERROR, Bundle().apply { putString("error", "Invalid Input Argument"); putInt("errorCode", ERROR) }) - 2 -> receiver?.send(TIMEOUT, Bundle().apply { putString("error", "Session Timeout"); putInt("errorCode", TIMEOUT) }) - 7 -> receiver?.send(RECAPTCHA_INVALID_SITEKEY, Bundle().apply { putString("error", "Invalid Site Key"); putInt("errorCode", RECAPTCHA_INVALID_SITEKEY) }) - 8 -> receiver?.send(RECAPTCHA_INVALID_KEYTYPE, Bundle().apply { putString("error", "Invalid Type of Site Key"); putInt("errorCode", RECAPTCHA_INVALID_KEYTYPE) }) - 9 -> receiver?.send(RECAPTCHA_INVALID_PACKAGE_NAME, Bundle().apply { putString("error", "Invalid Package Name for App"); putInt("errorCode", RECAPTCHA_INVALID_PACKAGE_NAME) }) - else -> receiver?.send(ERROR, Bundle().apply { putString("error", "error"); putInt("errorCode", ERROR) }) + 1 -> sendErrorResult(ERROR, "Invalid Input Argument") + 2 -> sendErrorResult(TIMEOUT, "Session Timeout") + 7 -> sendErrorResult(RECAPTCHA_INVALID_SITEKEY, "Invalid Site Key") + 8 -> sendErrorResult(RECAPTCHA_INVALID_KEYTYPE, "Invalid Type of Site Key") + 9 -> sendErrorResult(RECAPTCHA_INVALID_PACKAGE_NAME, "Invalid Package Name for App") + else -> sendErrorResult(ERROR, "error") } if (finish) this@ReCaptchaActivity.finish() } @@ -162,7 +163,8 @@ class ReCaptchaActivity : AppCompatActivity() { @JavascriptInterface fun verifyCallback(token: String) { Log.d(TAG, "verifyCallback($token)") - receiver?.send(0, Bundle().apply { putString("token", token) }) + sendResult(0) { putString("token", token) } + resultSent = true finish() } }, "RecaptchaEmbedder") @@ -172,6 +174,23 @@ class ReCaptchaActivity : AppCompatActivity() { } } + fun sendErrorResult(errorCode: Int, error: String) = sendResult(errorCode) { putString("error", error); putInt("errorCode", errorCode) } + + fun sendResult(resultCode: Int, v: Bundle.() -> Unit) { + receiver?.send(resultCode, Bundle().also(v)) + resultSent = true + } + + override fun finish() { + lifecycleScope.launchWhenResumed { + webView?.loadUrl("javascript: RecaptchaMFrame.shown(0, 0, false);") + } + if (!resultSent) { + sendErrorResult(CANCELED, "Cancelled") + } + super.finish() + } + fun setWebViewSize(width: Int, height: Int, visible: Boolean) { webView?.apply { layoutParams.width = min(widthPixels, (width * density).toInt()) @@ -183,7 +202,12 @@ class ReCaptchaActivity : AppCompatActivity() { suspend fun updateToken(flow: String, params: String) { val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP)) - val dg = Base64.encodeToString(DroidGuardResultCreator.getResult(this, flow, map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) + val dg = try { + Base64.encodeToString(DroidGuardResultCreator.getResult(this, flow, map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) + } catch (e: Exception) { + Log.w(TAG, e) + Base64.encodeToString("ERROR : IOException".toByteArray(), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) + } if (SDK_INT >= 19) { webView?.evaluateJavascript("RecaptchaMFrame.token('${URLEncoder.encode(dg, "UTF-8")}', '$params');", null) } else { @@ -194,7 +218,12 @@ class ReCaptchaActivity : AppCompatActivity() { suspend fun open() { val params = StringBuilder(params).appendUrlEncodedParam("mt", System.currentTimeMillis().toString()).toString() val map = mapOf("contentBinding" to Base64.encodeToString(MessageDigest.getInstance("SHA-256").digest(params.toByteArray()), Base64.NO_WRAP)) - val dg = Base64.encodeToString(DroidGuardResultCreator.getResult(this, "recaptcha-android-frame", map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) + val dg = try { + Base64.encodeToString(DroidGuardResultCreator.getResult(this, "recaptcha-android-frame", map), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) + } catch (e: Exception) { + Log.w(TAG, e) + Base64.encodeToString("ERROR : IOException".toByteArray(), Base64.NO_WRAP + Base64.URL_SAFE + Base64.NO_PADDING) + } webView?.postUrl(MFRAME_URL, "mav=1&dg=${URLEncoder.encode(dg, "UTF-8")}&mp=${URLEncoder.encode(params, "UTF-8")}".toByteArray()) } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt similarity index 62% rename from play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt rename to play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt index 69e745f6..228d4ec8 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt +++ b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2021 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.safetynet import android.content.Context import android.content.Intent +import android.database.Cursor import android.os.Build.VERSION.SDK_INT import android.os.Bundle import android.os.Parcel @@ -15,24 +16,24 @@ import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope -import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Status import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks import com.google.android.gms.safetynet.AttestationData import com.google.android.gms.safetynet.RecaptchaResultData +import com.google.android.gms.safetynet.SafetyNetStatusCodes import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks import com.google.android.gms.safetynet.internal.ISafetyNetService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.BaseService -import org.microg.gms.checkin.LastCheckinInfo import org.microg.gms.common.GmsService import org.microg.gms.common.PackageUtils -import org.microg.gms.droidguard.DroidGuardPreferences -import org.microg.gms.droidguard.DroidGuardResultCreator -import org.microg.gms.recaptcha.ReCaptchaActivity -import org.microg.gms.recaptcha.appendUrlEncodedParam +import org.microg.gms.droidguard.core.DroidGuardPreferences +import org.microg.gms.droidguard.core.DroidGuardResultCreator +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.CheckIn.getContentUri +import org.microg.gms.settings.SettingsContract.getSettings import java.io.IOException import java.util.* @@ -55,19 +56,19 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa override fun attestWithApiKey(callbacks: ISafetyNetCallbacks, nonce: ByteArray?, apiKey: String) { if (nonce == null) { - callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) + callbacks.onAttestationData(Status(SafetyNetStatusCodes.DEVELOPER_ERROR, "ApiKey missing"), null) return } - if (!SafetyNetPrefs.get(context).isEnabled) { + if (!SafetyNetPreferences.isEnabled(context)) { Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") - callbacks.onAttestationData(Status.CANCELED, null) + callbacks.onAttestationData(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null) return } if (!DroidGuardPreferences.isEnabled(context)) { Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled") - callbacks.onAttestationData(Status.CANCELED, null) + callbacks.onAttestationData(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null) return } @@ -78,11 +79,15 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa val data = mapOf("contentBinding" to attestation.payloadHashBase64) val dg = withContext(Dispatchers.IO) { DroidGuardResultCreator.getResult(context, "attest", data) } attestation.setDroidGaurdResult(Base64.encodeToString(dg, Base64.NO_WRAP + Base64.NO_PADDING + Base64.URL_SAFE)) - val resultData = withContext(Dispatchers.IO) { AttestationData(attestation.attest(apiKey)) } - callbacks.onAttestationData(Status.SUCCESS, resultData) + val jwsResult = withContext(Dispatchers.IO) { attestation.attest(apiKey) } + callbacks.onAttestationData(Status.SUCCESS, AttestationData(jwsResult)) } catch (e: Exception) { - Log.w(TAG, e) - callbacks.onAttestationData(Status.INTERNAL_ERROR, null) + Log.w(TAG, "Exception during attest: ${e.javaClass.name}", e) + val code = when(e) { + is IOException -> SafetyNetStatusCodes.NETWORK_ERROR + else -> SafetyNetStatusCodes.ERROR + } + callbacks.onAttestationData(Status(code, e.localizedMessage), null) } } } @@ -112,13 +117,19 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa override fun verifyWithRecaptcha(callbacks: ISafetyNetCallbacks, siteKey: String?) { if (siteKey == null) { - callbacks.onAttestationData(Status(CommonStatusCodes.DEVELOPER_ERROR), null) + callbacks.onAttestationData(Status(SafetyNetStatusCodes.RECAPTCHA_INVALID_SITEKEY, "SiteKey missing"), null) return } - if (!SafetyNetPrefs.get(context).isEnabled) { + if (!SafetyNetPreferences.isEnabled(context)) { Log.d(TAG, "ignoring SafetyNet request, SafetyNet is disabled") - callbacks.onAttestationData(Status.CANCELED, null) + callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null) + return + } + + if (!DroidGuardPreferences.isEnabled(context)) { + Log.d(TAG, "ignoring SafetyNet request, DroidGuard is disabled") + callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, "Disabled"), null) return } @@ -126,16 +137,38 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + val androidId = getSettings(context, getContentUri(context), arrayOf(SettingsContract.CheckIn.ANDROID_ID)) { cursor: Cursor -> cursor.getLong(0) } val params = StringBuilder() + val packageFileDigest = try { + Base64.encodeToString(Attestation.getPackageFileDigest(context, packageName), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING) + } catch (e: Exception) { + if (packageName == "com.blogspot.android_er.recaptcha") { + "kXkOWm-DT-q__5MnrdyCRLowptdd2PjNA1RAnyQ1A-4" + } else { + callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, e.localizedMessage), null) + return + } + } + val packageSignatures = try { + Attestation.getPackageSignatures(context, packageName).map { Base64.encodeToString(it, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING) } + } catch (e: Exception) { + if (packageName == "com.blogspot.android_er.recaptcha") { + listOf("xgEpqm72luj7TLUt7kMxIyN-orV6v03_T_yCkR4A93Y") + } else { + callbacks.onRecaptchaResult(Status(SafetyNetStatusCodes.ERROR, e.localizedMessage), null) + return + } + } params.appendUrlEncodedParam("k", siteKey) - .appendUrlEncodedParam("di", LastCheckinInfo.read(context).androidId.toString()) + .appendUrlEncodedParam("di", androidId.toString()) .appendUrlEncodedParam("pk", packageName) .appendUrlEncodedParam("sv", SDK_INT.toString()) .appendUrlEncodedParam("gv", "20.47.14 (040306-{{cl}})") .appendUrlEncodedParam("gm", "260") - .appendUrlEncodedParam("as", Base64.encodeToString(Attestation.getPackageFileDigest(context, packageName), Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)) - for (signature in Attestation.getPackageSignatures(context, packageName)) { - params.appendUrlEncodedParam("ac", Base64.encodeToString(signature, Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING)) + .appendUrlEncodedParam("as", packageFileDigest) + for (signature in packageSignatures) { + Log.d(TAG, "Sig: $signature") + params.appendUrlEncodedParam("ac", signature) } params.appendUrlEncodedParam("ip", "com.android.vending") .appendUrlEncodedParam("av", false.toString()) diff --git a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetPreferences.kt b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetPreferences.kt new file mode 100644 index 00000000..a2a9e827 --- /dev/null +++ b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetPreferences.kt @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.safetynet + +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import org.microg.gms.settings.SettingsContract +import org.microg.gms.settings.SettingsContract.SafetyNet.ENABLED + +object SafetyNetPreferences { + private fun getSettings(context: Context, projection: String, def: T, f: (Cursor) -> T): T { + return try { + SettingsContract.getSettings(context, SettingsContract.SafetyNet.getContentUri(context), arrayOf(projection), f) + } catch (e: Exception) { + def + } + } + + private fun setSettings(context: Context, f: ContentValues.() -> Unit) = + SettingsContract.setSettings(context, SettingsContract.SafetyNet.getContentUri(context), f) + + @JvmStatic + fun isEnabled(context: Context): Boolean = getSettings(context, ENABLED, false) { it.getInt(0) != 0 } + + @JvmStatic + fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) } +} diff --git a/play-services-core/src/main/res/drawable/ic_recaptcha.xml b/play-services-safetynet-core/src/main/res/drawable/ic_recaptcha.xml similarity index 100% rename from play-services-core/src/main/res/drawable/ic_recaptcha.xml rename to play-services-safetynet-core/src/main/res/drawable/ic_recaptcha.xml diff --git a/play-services-core/src/main/res/layout/recaptcha_window.xml b/play-services-safetynet-core/src/main/res/layout/recaptcha_window.xml similarity index 100% rename from play-services-core/src/main/res/layout/recaptcha_window.xml rename to play-services-safetynet-core/src/main/res/layout/recaptcha_window.xml From d593de25ef4d253d57dfc6cf62f9d7a1a05f383c Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:44:18 +0100 Subject: [PATCH 37/63] Update dependencies --- build.gradle | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 92d7db8c..14a8ecde 100644 --- a/build.gradle +++ b/build.gradle @@ -6,37 +6,35 @@ buildscript { ext.cronetVersion = '91.0.4472.120.1' ext.nlpVersion = '2.0-alpha6' - ext.remoteDroidGuardVersion = '0.1.2' ext.safeParcelVersion = '1.7.0' ext.wearableVersion = '0.1.1' - ext.kotlinVersion = '1.4.32' + ext.kotlinVersion = '1.6.10' ext.coroutineVersion = '1.5.2' ext.annotationVersion = '1.2.0' - ext.appcompatVersion = '1.2.0' - ext.coreVersion = '1.3.0' - ext.fragmentVersion = '1.2.5' - ext.lifecycleVersion = '2.3.1' - ext.mediarouterVersion = '1.2.2' + ext.appcompatVersion = '1.4.0' + ext.coreVersion = '1.7.0' + ext.fragmentVersion = '1.4.0' + ext.lifecycleVersion = '2.4.0' + ext.mediarouterVersion = '1.2.5' ext.multidexVersion = '2.0.1' ext.navigationVersion = '2.3.5' ext.preferenceVersion = '1.1.1' - ext.recyclerviewVersion = '1.1.0' + ext.recyclerviewVersion = '1.2.0' ext.webkitVersion = '1.4.0' - ext.supportLibraryVersion = '28.0.0' ext.slf4jVersion = '1.7.25' ext.volleyVersion = '1.2.1' ext.wireVersion = '3.2.2' - ext.androidBuildGradleVersion = '4.1.0' + ext.androidBuildGradleVersion = '7.0.4' ext.androidBuildVersionTools = '30.0.2' ext.androidMinSdk = 14 ext.androidTargetSdk = 29 - ext.androidCompileSdk = 30 + ext.androidCompileSdk = 31 repositories { jcenter() From 0bdcb1319bd03f7f79ab640bd3379cd8f37fcc7c Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:45:51 +0100 Subject: [PATCH 38/63] Update location service --- build.gradle | 2 +- .../gms/location/DeviceOrientation.aidl | 3 + .../location/IDeviceOrientationListener.aidl | 7 + .../DeviceOrientationRequestUpdateData.aidl | 3 + .../IGoogleLocationManagerService.aidl | 39 ++++- .../gms/location/DeviceOrientation.java | 118 ++++++++++++++ .../location/DeviceOrientationRequest.java | 39 +++++ .../android/gms/location/LocationRequest.java | 144 ++++++++++++++---- .../DeviceOrientationRequestInternal.java | 35 +++++ .../DeviceOrientationRequestUpdateData.java | 39 +++++ .../internal/LocationRequestInternal.java | 34 +++-- play-services-location-core/build.gradle | 15 +- .../gms/location/GoogleLocationManager.java | 84 +++++++--- .../GoogleLocationManagerService.java | 12 +- .../GoogleLocationManagerServiceImpl.java | 110 ++++++++++--- .../gms/location/LocationRequestHelper.java | 12 +- .../gms/location/RealLocationProvider.java | 22 ++- .../gms/location/ReportingServiceImpl.java | 12 ++ .../gms/location/UnifiedLocationProvider.kt | 110 +++++++------ .../location/NativeLocationClientImpl.java | 4 +- 20 files changed, 688 insertions(+), 156 deletions(-) create mode 100644 play-services-location-api/src/main/aidl/com/google/android/gms/location/DeviceOrientation.aidl create mode 100644 play-services-location-api/src/main/aidl/com/google/android/gms/location/IDeviceOrientationListener.aidl create mode 100644 play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.aidl create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientation.java create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestInternal.java create mode 100644 play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.java diff --git a/build.gradle b/build.gradle index 14a8ecde..e873b2bb 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.cronetVersion = '91.0.4472.120.1' - ext.nlpVersion = '2.0-alpha6' + ext.nlpVersion = '2.0-alpha7' ext.safeParcelVersion = '1.7.0' ext.wearableVersion = '0.1.1' diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/DeviceOrientation.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/DeviceOrientation.aidl new file mode 100644 index 00000000..3055c41b --- /dev/null +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/DeviceOrientation.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.location; + +parcelable DeviceOrientation; diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/IDeviceOrientationListener.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/IDeviceOrientationListener.aidl new file mode 100644 index 00000000..e3d3e6f0 --- /dev/null +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/IDeviceOrientationListener.aidl @@ -0,0 +1,7 @@ +package com.google.android.gms.location; + +import com.google.android.gms.location.DeviceOrientation; + +interface IDeviceOrientationListener { + void onDeviceOrientationChanged(in DeviceOrientation deviceOrientation); +} diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.aidl new file mode 100644 index 00000000..e92b0f0d --- /dev/null +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.location.internal; + +parcelable DeviceOrientationRequestUpdateData; diff --git a/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/IGoogleLocationManagerService.aidl b/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/IGoogleLocationManagerService.aidl index 8e78a899..19f9be10 100644 --- a/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/IGoogleLocationManagerService.aidl +++ b/play-services-location-api/src/main/aidl/com/google/android/gms/location/internal/IGoogleLocationManagerService.aidl @@ -5,8 +5,10 @@ import android.location.Location; import android.os.Bundle; import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.api.internal.IStatusCallback; import com.google.android.gms.location.places.AutocompleteFilter; import com.google.android.gms.location.places.internal.IPlacesCallbacks; +import com.google.android.gms.location.internal.DeviceOrientationRequestUpdateData; import com.google.android.gms.location.internal.ISettingsCallbacks; import com.google.android.gms.location.internal.LocationRequestInternal; import com.google.android.gms.location.internal.LocationRequestUpdateData; @@ -36,17 +38,19 @@ interface IGoogleLocationManagerService { void removeGeofencesByIntent(in PendingIntent pendingIntent, IGeofencerCallbacks callbacks, String packageName) = 1; void removeGeofencesById(in String[] geofenceRequestIds, IGeofencerCallbacks callbacks, String packageName) = 2; void removeAllGeofences(IGeofencerCallbacks callbacks, String packageName) = 3; +// void removeGeofences(in RemoveGeofencingRequest request, IGeofencerCallbacks callback) = 73; void requestActivityUpdates(long detectionIntervalMillis, boolean alwaysTrue, in PendingIntent callbackIntent) = 4; void removeActivityUpdates(in PendingIntent callbackIntent) = 5; ActivityRecognitionResult getLastActivity(String packageName) = 63; - Status iglms65(in PendingIntent pendingIntent) = 64; - Status iglms66(in PendingIntent pendingIntent) = 65; Status requestGestureUpdates(in GestureRequest request, in PendingIntent pendingIntent) = 59; Status iglms61(in PendingIntent pendingIntent) = 60; Location getLastLocation() = 6; + Location getLastLocationWithPackage(String packageName) = 20; + Location getLastLocationWith(String s) = 79; + void requestLocationUpdatesWithListener(in LocationRequest request, ILocationListener listener) = 7; void requestLocationUpdatesWithPackage(in LocationRequest request, ILocationListener listener, String packageName) = 19; void requestLocationUpdatesWithIntent(in LocationRequest request, in PendingIntent callbackIntent) = 8; @@ -55,34 +59,55 @@ interface IGoogleLocationManagerService { void removeLocationUpdatesWithListener(ILocationListener listener) = 9; void removeLocationUpdatesWithIntent(in PendingIntent callbackIntent) = 10; void updateLocationRequest(in LocationRequestUpdateData locationRequestUpdateData) = 58; - //void flushLocations(IFusedLocationProviderCallback callback = 66; +// void flushLocations(IFusedLocationProviderCallback callback) = 66; void setMockMode(boolean mockMode) = 11; void setMockLocation(in Location mockLocation) = 12; + void injectLocation(in Location mockLocation, int injectionType) = 25; - Location getLastLocationWithPackage(String packageName) = 20; - void iglms26(in Location var1, int var2) = 25; LocationAvailability getLocationAvailabilityWithPackage(String packageName) = 33; - IBinder iglms51() = 50; +// void requestSleepSegmentUpdates(in PendingIntent pendingIntent, in SleepSegmentRequest request, IStatusCallback callback) = 78; + void removeSleepSegmentUpdates(in PendingIntent pendingIntent, IStatusCallback callback) = 68; + void requestLocationSettingsDialog(in LocationSettingsRequest settingsRequest, ISettingsCallbacks callback, String packageName) = 62; +// void requestActivityTransitionUpdates(in ActivityTransitionRequest request, in PendingIntent pendingIntent, IStatusCallback callback) = 71; + void removeActivityTransitionUpdates(in PendingIntent pendingIntent, IStatusCallback callback) = 72; + + void updateDeviceOrientationRequest(in DeviceOrientationRequestUpdateData request) = 74; + + boolean setActivityRecognitionMode(int mode) = 76; + void iglms14(in LatLngBounds var1, int var2, in PlaceFilter var3, in PlacesParams var4, IPlacesCallbacks var5) = 13; void iglms15(String var1, in PlacesParams var2, IPlacesCallbacks var3) = 14; void iglms16(in LatLng var1, in PlaceFilter var2, in PlacesParams var3, IPlacesCallbacks var4) = 15; void iglms17(in PlaceFilter var1, in PlacesParams var2, IPlacesCallbacks var3) = 16; void iglms18(in PlaceRequest var1, in PlacesParams var2, in PendingIntent var3) = 17; void iglms19(in PlacesParams var1, in PendingIntent var2) = 18; + void iglms25(in PlaceReport var1, in PlacesParams var2) = 24; + void iglms42(String var1, in PlacesParams var2, IPlacesCallbacks var3) = 41; + void iglms46(in UserAddedPlace var1, in PlacesParams var2, IPlacesCallbacks var3) = 45; void iglms47(in LatLngBounds var1, int var2, String var3, in PlaceFilter var4, in PlacesParams var5, IPlacesCallbacks var6) = 46; void iglms48(in NearbyAlertRequest var1, in PlacesParams var2, in PendingIntent var3) = 47; void iglms49(in PlacesParams var1, in PendingIntent var2) = 48; void iglms50(in UserDataType var1, in LatLngBounds var2, in List var3, in PlacesParams var4, IPlacesCallbacks var5) = 49; + IBinder iglms51() = 50; + IBinder iglms54() = 53; void iglms55(String var1, in LatLngBounds var2, in AutocompleteFilter var3, in PlacesParams var4, IPlacesCallbacks var5) = 54; + void iglms58(in List var1, in PlacesParams var2, IPlacesCallbacks var3) = 57; - //void updateDeviceOrientationRequest(in DeviceOrientationRequestUpdateData request) = 74; + void iglms65(in PendingIntent pendingIntent, IStatusCallback callback) = 64; + void iglms66(in PendingIntent pendingIntent, IStatusCallback callback) = 65; + + void iglms68(in PendingIntent pendingIntent, IStatusCallback callback) = 67; +// void iglms70(in ActivityRecognitionRequest request, in PendingIntent pendingIntent, IStatusCallback callback) = 69; + void iglms71(IStatusCallback callback) = 70; + void iglms76(in PendingIntent pendingIntent) = 75; + int iglms78() = 77; } diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientation.java b/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientation.java new file mode 100644 index 00000000..61373df0 --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientation.java @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location; + +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.Arrays; + +public class DeviceOrientation extends AutoSafeParcelable { + @Field(1) + private float[] attitude = new float[4]; + @Field(2) + private int attitudeConfidence = -1; + @Field(3) + private int magConfidence = -1; + @Field(4) + private float headingDegrees = Float.NaN; + @Field(5) + private float headingErrorDegrees = Float.NaN; + @Field(6) + private long elapsedRealtimeNanos = 0; + @Field(7) + private byte flags = 0; + @Field(8) + private float conservativeHeadingErrorVonMisesKappa = Float.NaN; + + public float[] getAttitude() { + if ((flags & 0x10) != 0) return attitude; + return new float[4]; + } + + public void setAttitude(float[] attitude) { + if (attitude.length != 4) throw new IllegalArgumentException(); + this.attitude = attitude; + flags = (byte) (flags | 0x10); + } + + public int getAttitudeConfidence() { + if ((flags & 0x1) != 0) return attitudeConfidence; + return -1; + } + + public void setAttitudeConfidence(int attitudeConfidence) { + this.attitudeConfidence = attitudeConfidence; + flags = (byte) (flags | 0x1); + } + + public int getMagConfidence() { + if ((flags & 0x2) != 0) return magConfidence; + return -1; + } + + public void setMagConfidence(int magConfidence) { + this.magConfidence = magConfidence; + flags = (byte) (flags | 0x2); + } + + public float getHeadingDegrees() { + if ((flags & 0x4) != 0) return headingDegrees; + return Float.NaN; + } + + public void setHeadingDegrees(float headingDegrees) { + this.headingDegrees = headingDegrees; + flags = (byte) (flags | 0x4); + } + + public float getHeadingErrorDegrees() { + if ((flags & 0x8) != 0) return headingErrorDegrees; + return Float.NaN; + } + + public void setHeadingErrorDegrees(float headingErrorDegrees) { + this.headingErrorDegrees = headingErrorDegrees; + flags = (byte) (flags | 0x8); + } + + public float getConservativeHeadingErrorVonMisesKappa() { + if ((flags & 0x20) != 0) return conservativeHeadingErrorVonMisesKappa; + return Float.NaN; + } + + public void setConservativeHeadingErrorVonMisesKappa(float conservativeHeadingErrorVonMisesKappa) { + this.conservativeHeadingErrorVonMisesKappa = conservativeHeadingErrorVonMisesKappa; + flags = (byte) (flags | 0x20); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("DeviceOrientation{"); + if ((flags & 0x10) != 0) + sb.append("attitude=").append(Arrays.toString(attitude)); + if ((flags & 0x1) != 0) + sb.append(", attitudeConfidence=").append(attitudeConfidence); + if ((flags & 0x2) != 0) + sb.append(", magConfidence=").append(magConfidence); + if ((flags & 0x4) != 0) + sb.append(", headingDegrees=").append(headingDegrees); + if ((flags & 0x8) != 0) + sb.append(", headingErrorDegrees=").append(headingErrorDegrees); + return "DeviceOrientation{" + + "attitude=" + Arrays.toString(attitude) + + ", attitudeConfidence=" + attitudeConfidence + + ", magConfidence=" + magConfidence + + ", headingDegrees=" + headingDegrees + + ", headingErrorDegrees=" + headingErrorDegrees + + ", elapsedRealtimeNanos=" + elapsedRealtimeNanos + + ", flags=" + flags + + ", conservativeHeadingErrorVonMisesKappa=" + conservativeHeadingErrorVonMisesKappa + + '}'; + } + + public static final Creator CREATOR = new AutoCreator(DeviceOrientation.class); +} diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java new file mode 100644 index 00000000..14ce93c1 --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/DeviceOrientationRequest.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location; + +import android.os.SystemClock; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class DeviceOrientationRequest extends AutoSafeParcelable { + @Field(1) + public boolean shouldUseMag; + @Field(2) + public long minimumSamplingPeriodMs; + @Field(3) + public float smallesAngleChangeRadians; + @Field(4) + public long expirationTime; + @Field(5) + public int numUpdates; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Request[shouldUseMag=").append(shouldUseMag); + sb.append(" minimumSamplingPeriod=").append(minimumSamplingPeriodMs).append("ms"); + sb.append(" smallesAngleChange=").append(smallesAngleChangeRadians).append("rad"); + if (expirationTime != Long.MAX_VALUE) + sb.append(" expireIn=").append(SystemClock.elapsedRealtime() - expirationTime).append("ms"); + if (numUpdates != Integer.MAX_VALUE) + sb.append(" num=").append(numUpdates); + sb.append("]"); + return sb.toString(); + } + + public static final Creator CREATOR = new AutoCreator(DeviceOrientationRequest.class); +} diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java b/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java index 15d159e8..085886c0 100644 --- a/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/LocationRequest.java @@ -17,8 +17,8 @@ package com.google.android.gms.location; import android.os.SystemClock; + import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; import java.util.Arrays; @@ -101,24 +101,26 @@ public class LocationRequest extends AutoSafeParcelable { */ public static final int PRIORITY_NO_POWER = 105; - @SafeParceled(1000) + @Field(1000) private int versionCode = 1; - @SafeParceled(1) + @Field(1) private int priority; - @SafeParceled(2) + @Field(2) private long interval; - @SafeParceled(3) + @Field(3) private long fastestInterval; - @SafeParceled(4) + @Field(4) private boolean explicitFastestInterval; - @SafeParceled(5) + @Field(5) private long expirationTime; - @SafeParceled(6) + @Field(6) private int numUpdates; - @SafeParceled(7) - private float smallestDesplacement; - @SafeParceled(8) + @Field(7) + private float smallestDisplacement; + @Field(8) private long maxWaitTime; + @Field(9) + private boolean waitForAccurateLocation; public LocationRequest() { this.priority = PRIORITY_BALANCED_POWER_ACCURACY; @@ -127,7 +129,7 @@ public class LocationRequest extends AutoSafeParcelable { this.explicitFastestInterval = false; this.expirationTime = Long.MAX_VALUE; this.numUpdates = Integer.MAX_VALUE; - this.smallestDesplacement = 0; + this.smallestDisplacement = 0; this.maxWaitTime = 0; } @@ -176,6 +178,17 @@ public class LocationRequest extends AutoSafeParcelable { return interval; } + /** + * Gets the maximum wait time in milliseconds for location updates. If the wait time is smaller than the interval + * requested with {@link #setInterval(long)}, then the interval will be used instead. + * + * @return maximum wait time in milliseconds, inexact + * @see #setMaxWaitTime(long) + */ + public long getMaxWaitTime() { + return maxWaitTime; + } + /** * Get the number of updates requested. *

@@ -204,8 +217,8 @@ public class LocationRequest extends AutoSafeParcelable { * * @return minimum displacement between location updates in meters */ - public float getSmallestDesplacement() { - return smallestDesplacement; + public float getSmallestDisplacement() { + return smallestDisplacement; } @Override @@ -231,7 +244,7 @@ public class LocationRequest extends AutoSafeParcelable { return false; if (priority != that.priority) return false; - if (Float.compare(that.smallestDesplacement, smallestDesplacement) != 0) + if (Float.compare(that.smallestDisplacement, smallestDisplacement) != 0) return false; return true; @@ -240,11 +253,28 @@ public class LocationRequest extends AutoSafeParcelable { @Override public int hashCode() { return Arrays.hashCode( - new Object[] { priority, interval, fastestInterval, explicitFastestInterval, - explicitFastestInterval, numUpdates, smallestDesplacement, maxWaitTime + new Object[]{priority, interval, fastestInterval, explicitFastestInterval, + explicitFastestInterval, numUpdates, smallestDisplacement, maxWaitTime }); } + /** + * Returns whether or not the fastest interval was explicitly specified for the location request. + * + * @return True if the fastest interval was explicitly set for the location request; false otherwise + */ + public boolean isFastestIntervalExplicitlySet() { + return explicitFastestInterval; + } + + /** + * Returns whether the location services will wait a few seconds initially for accurate locations, if accurate + * locations cannot be computed on the device for {@link #PRIORITY_HIGH_ACCURACY} requests. + */ + public boolean isWaitForAccurateLocation() { + return waitForAccurateLocation; + } + /** * Set the duration of this request, in milliseconds. *

@@ -312,6 +342,7 @@ public class LocationRequest extends AutoSafeParcelable { if (millis < 0) throw new IllegalArgumentException("interval must not be negative"); fastestInterval = millis; + explicitFastestInterval = true; return this; } @@ -348,6 +379,27 @@ public class LocationRequest extends AutoSafeParcelable { return this; } + /** + * Sets the maximum wait time in milliseconds for location updates. + *

+ * If you pass a value at least 2x larger than the interval specified with {@link #setInterval(long)}, then + * location delivery may be delayed and multiple locations can be delivered at once. Locations are determined at + * the {@link #setInterval(long)} rate, but can be delivered in batch after the interval you set in this method. + * This can consume less battery and give more accurate locations, depending on the device's hardware capabilities. + * You should set this value to be as large as possible for your needs if you don't need immediate location + * delivery. + * + * @param millis desired maximum wait time in millisecond, inexact + * @return the same object, so that setters can be chained + * @throws IllegalArgumentException if the interval is less than zero + */ + public LocationRequest setMaxWaitTime(long millis) throws IllegalArgumentException { + if (millis < 0) + throw new IllegalArgumentException("interval must not be negative"); + maxWaitTime = millis; + return this; + } + /** * Set the number of location updates. *

@@ -417,22 +469,58 @@ public class LocationRequest extends AutoSafeParcelable { public LocationRequest setSmallestDisplacement(float smallestDisplacementMeters) { if (smallestDisplacementMeters < 0) throw new IllegalArgumentException("smallestDisplacementMeters must not be negative"); - this.smallestDesplacement = smallestDisplacementMeters; + this.smallestDisplacement = smallestDisplacementMeters; return this; } + /** + * Sets whether the client wants the locations services to wait a few seconds for accurate locations initially, + * when accurate locations could not be computed on the device immediately after {@link #PRIORITY_HIGH_ACCURACY} + * request is made. By default the location services will wait for accurate locations. + *

+ * Note that this only applies to clients with {@link #PRIORITY_HIGH_ACCURACY} requests. + *

+ * Also note this only applies to the initial locations computed right after the location request is added. The + * following inaccurate locations may still be delivered to the clients without delay. + */ + public LocationRequest setWaitForAccurateLocation(boolean waitForAccurateLocation) { + this.waitForAccurateLocation = waitForAccurateLocation; + return this; + } + + private static String priorityToString(int priority) { + switch (priority) { + case PRIORITY_HIGH_ACCURACY: + return "PRIORITY_HIGH_ACCURACY"; + case PRIORITY_BALANCED_POWER_ACCURACY: + return "PRIORITY_BALANCED_POWER_ACCURACY"; + case PRIORITY_LOW_POWER: + return "PRIORITY_LOW_POWER"; + case PRIORITY_NO_POWER: + return "PRIORITY_NO_POWER"; + default: + return "???"; + } + } + @Override public String toString() { - return "LocationRequest{" + - "priority=" + priority + - ", interval=" + interval + - ", fastestInterval=" + fastestInterval + - ", explicitFastestInterval=" + explicitFastestInterval + - ", expirationTime=" + expirationTime + - ", numUpdates=" + numUpdates + - ", smallestDesplacement=" + smallestDesplacement + - ", maxWaitTime=" + maxWaitTime + - '}'; + StringBuilder sb = new StringBuilder(); + sb.append("Request["); + sb.append(priorityToString(priority)); + if (priority != PRIORITY_NO_POWER) + sb.append(" requested=").append(interval).append("ms"); + sb.append(" fastest=").append(fastestInterval).append("ms"); + if (maxWaitTime > interval) + sb.append(" maxWait=").append(maxWaitTime).append("ms"); + if (smallestDisplacement > 0) + sb.append(" smallestDisplacement=").append(smallestDisplacement).append("m"); + if (expirationTime != Long.MAX_VALUE) + sb.append(" expireIn=").append(SystemClock.elapsedRealtime() - expirationTime).append("ms"); + if (numUpdates != Integer.MAX_VALUE) + sb.append(" num=").append(numUpdates); + sb.append("]"); + return sb.toString(); } public static final Creator CREATOR = new AutoCreator(LocationRequest.class); diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestInternal.java b/play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestInternal.java new file mode 100644 index 00000000..5b66feaa --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestInternal.java @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location.internal; + +import com.google.android.gms.location.DeviceOrientationRequest; + +import org.microg.safeparcel.AutoSafeParcelable; + +import java.util.List; + +public class DeviceOrientationRequestInternal extends AutoSafeParcelable { + + @Field(1) + public DeviceOrientationRequest request; + + @Field(value = 2, subClass = ClientIdentity.class) + public List clients; + + @Field(3) + public String tag; + + @Override + public String toString() { + return "DeviceOrientationRequestInternal{" + + "request=" + request + + ", clients=" + clients + + ", tag='" + tag + '\'' + + '}'; + } + + public static final Creator CREATOR = new AutoCreator(DeviceOrientationRequestInternal.class); +} diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.java b/play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.java new file mode 100644 index 00000000..5a2e759e --- /dev/null +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/internal/DeviceOrientationRequestUpdateData.java @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.location.internal; + +import com.google.android.gms.location.IDeviceOrientationListener; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class DeviceOrientationRequestUpdateData extends AutoSafeParcelable { + public static final int REQUEST_UPDATES = 1; + public static final int REMOVE_UPDATES = 2; + + @Field(1) + public int opCode; + + @Field(2) + public DeviceOrientationRequestInternal request; + + @Field(3) + public IDeviceOrientationListener listener; + + @Field(4) + public IFusedLocationProviderCallback fusedLocationProviderCallback; + + @Override + public String toString() { + return "DeviceOrientationRequestUpdateData{" + + "opCode=" + opCode + + ", request=" + request + + ", listener=" + (listener != null ? listener.asBinder() : null) + + ", fusedLocationProviderCallback=" + (fusedLocationProviderCallback != null ? fusedLocationProviderCallback.asBinder() : null) + + '}'; + } + + public static final Creator CREATOR = new AutoCreator(DeviceOrientationRequestUpdateData.class); +} diff --git a/play-services-location-api/src/main/java/com/google/android/gms/location/internal/LocationRequestInternal.java b/play-services-location-api/src/main/java/com/google/android/gms/location/internal/LocationRequestInternal.java index dd70d96a..ee2e09a6 100644 --- a/play-services-location-api/src/main/java/com/google/android/gms/location/internal/LocationRequestInternal.java +++ b/play-services-location-api/src/main/java/com/google/android/gms/location/internal/LocationRequestInternal.java @@ -25,39 +25,48 @@ import java.util.List; public class LocationRequestInternal extends AutoSafeParcelable { - @SafeParceled(1000) + @Field(1000) private int versionCode = 1; - @SafeParceled(1) + @Field(1) public LocationRequest request; - @SafeParceled(2) + @Field(2) @Deprecated public boolean requestNlpDebugInfo; - @SafeParceled(3) + @Field(3) @Deprecated public boolean restorePendingIntentListeners; - @SafeParceled(4) + @Field(4) @Deprecated public boolean triggerUpdate; - @SafeParceled(value = 5, subClass = ClientIdentity.class) + @Field(value = 5, subClass = ClientIdentity.class) public List clients; - @SafeParceled(6) + @Field(6) public String tag; - @SafeParceled(7) + @Field(7) public boolean hideFromAppOps; - @SafeParceled(8) + @Field(8) public boolean forceCoarseLocation; - @SafeParceled(9) + @Field(9) public boolean exemptFromThrottle; - @SafeParceled(10) + @Field(10) public String moduleId; + @Field(11) + public boolean locationSettingsIgnored; + + @Field(12) + public boolean inaccurateLocationsDelayed; + + @Field(13) + public String contextAttributeTag; + @Override public String toString() { return "LocationRequestInternal{" + @@ -71,6 +80,9 @@ public class LocationRequestInternal extends AutoSafeParcelable { ", forceCoarseLocation=" + forceCoarseLocation + ", exemptFromThrottle=" + exemptFromThrottle + ", moduleId=" + moduleId + + ", locationSettingsIgnored=" + locationSettingsIgnored + + ", inaccurateLocationsDelayed=" + inaccurateLocationsDelayed + + ", contextAttributeTag=" + contextAttributeTag + '}'; } diff --git a/play-services-location-core/build.gradle b/play-services-location-core/build.gradle index bc36d2af..db9e8643 100644 --- a/play-services-location-core/build.gradle +++ b/play-services-location-core/build.gradle @@ -8,19 +8,16 @@ apply plugin: 'kotlin-android' dependencies { api project(':play-services-location-api') - - implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" - implementation project(':play-services-base-core') - implementation "org.microg.nlp:geocode-v1:$nlpVersion" - implementation "org.microg.nlp:location-v2:$nlpVersion" - implementation "org.microg.nlp:location-v3:$nlpVersion" - implementation "org.microg.nlp:service:$nlpVersion" + + runtimeOnly "org.microg.nlp:service:$nlpVersion" + api "org.microg.nlp:client:$nlpVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" - api "org.microg.nlp:client:$nlpVersion" - api "org.microg.nlp:ui:$nlpVersion" + implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" } android { diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java index 3ec7ac1b..f8c5d04e 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManager.java @@ -27,6 +27,7 @@ import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ILocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.internal.FusedLocationProviderResult; @@ -35,6 +36,7 @@ import com.google.android.gms.location.internal.LocationRequestUpdateData; import org.microg.gms.common.PackageUtils; import org.microg.gms.common.Utils; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -45,8 +47,10 @@ import static android.location.LocationManager.GPS_PROVIDER; import static com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY; import static com.google.android.gms.location.LocationRequest.PRIORITY_NO_POWER; +import androidx.lifecycle.Lifecycle; + public class GoogleLocationManager implements LocationChangeListener { - private static final String TAG = "GmsLocManager"; + private static final String TAG = "LocationManager"; private static final String MOCK_PROVIDER = "mock"; private static final long VERIFY_CURRENT_REQUESTS_INTERVAL_MS = 5000; // 5 seconds private static final long SWITCH_ON_FRESHNESS_CLIFF_MS = 30000; // 30 seconds @@ -60,7 +64,8 @@ public class GoogleLocationManager implements LocationChangeListener { private final MockLocationProvider mockProvider; private final List currentRequests = new ArrayList(); - public GoogleLocationManager(Context context) { + public GoogleLocationManager(Context context, Lifecycle lifecycle) { + long callingIdentity = Binder.clearCallingIdentity(); this.context = context; LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); if (Utils.hasSelfPermissionOrNotify(context, Manifest.permission.ACCESS_FINE_LOCATION)) { @@ -69,12 +74,13 @@ public class GoogleLocationManager implements LocationChangeListener { this.gpsProvider = null; } if (Utils.hasSelfPermissionOrNotify(context, Manifest.permission.ACCESS_COARSE_LOCATION)) { - this.networkProvider = new UnifiedLocationProvider(context, this); + this.networkProvider = new UnifiedLocationProvider(context, this, lifecycle); } else { this.networkProvider = null; } mockProvider = new MockLocationProvider(this); handler = new Handler(Looper.getMainLooper()); + Binder.restoreCallingIdentity(callingIdentity); } public void invokeOnceReady(Runnable runnable) { @@ -140,19 +146,29 @@ public class GoogleLocationManager implements LocationChangeListener { } } if (old != null) { + Log.d(TAG, "Removing replaced location request: " + old); currentRequests.remove(old); } currentRequests.add(request); if (gpsProvider != null && request.hasFinePermission() && request.locationRequest.getPriority() == PRIORITY_HIGH_ACCURACY) { + Log.d(TAG, "Registering request with high accuracy location provider"); gpsProvider.addRequest(request); } else if (gpsProvider != null && old != null) { + Log.d(TAG, "Unregistering request with high accuracy location provider"); gpsProvider.removeRequest(old); + } else { + Log.w(TAG, "Not providing high accuracy location: missing permission"); } if (networkProvider != null && request.hasCoarsePermission() && request.locationRequest.getPriority() != PRIORITY_NO_POWER) { + Log.d(TAG, "Registering request with low accuracy location provider"); networkProvider.addRequest(request); } else if (networkProvider != null && old != null) { + Log.d(TAG, "Unregistering request with low accuracy location provider"); networkProvider.removeRequest(old); + } else { + Log.w(TAG, "Not providing low accuracy location: missing permission"); } + handler.postDelayed(this::onLocationChanged, request.locationRequest.getFastestInterval()); } public void requestLocationUpdates(LocationRequest request, ILocationListener listener, String packageName) { @@ -188,28 +204,43 @@ public class GoogleLocationManager implements LocationChangeListener { } public void updateLocationRequest(LocationRequestUpdateData data) { - String packageName = PackageUtils.getCallingPackage(context); - if (data.pendingIntent != null) - packageName = PackageUtils.packageFromPendingIntent(data.pendingIntent); - if (data.opCode == LocationRequestUpdateData.REQUEST_UPDATES) { - requestLocationUpdates(new LocationRequestHelper(context, packageName, Binder.getCallingUid(), data)); - } else if (data.opCode == LocationRequestUpdateData.REMOVE_UPDATES) { - for (int i = 0; i < currentRequests.size(); i++) { - if (currentRequests.get(i).respondsTo(data.listener) - || currentRequests.get(i).respondsTo(data.pendingIntent) - || currentRequests.get(i).respondsTo(data.callback)) { - removeLocationUpdates(currentRequests.get(i)); - i--; + try { + Log.d(TAG, "updateLocationRequest: " + data); + String packageName = PackageUtils.getCallingPackage(context); + if (data.pendingIntent != null) + packageName = PackageUtils.packageFromPendingIntent(data.pendingIntent); + Log.d(TAG, "Using source package: " + packageName); + if (data.opCode == LocationRequestUpdateData.REQUEST_UPDATES) { + requestLocationUpdates(new LocationRequestHelper(context, packageName, Binder.getCallingUid(), data)); + } else if (data.opCode == LocationRequestUpdateData.REMOVE_UPDATES) { + for (int i = 0; i < currentRequests.size(); i++) { + if (currentRequests.get(i).respondsTo(data.listener) + || currentRequests.get(i).respondsTo(data.pendingIntent) + || currentRequests.get(i).respondsTo(data.callback)) { + removeLocationUpdates(currentRequests.get(i)); + i--; + } + } + } + Log.d(TAG, "Updated current requests, verifying"); + verifyCurrentRequests(); + if (data.fusedLocationProviderCallback != null) { + try { + Log.d(TAG, "Send success result to " + packageName); + data.fusedLocationProviderCallback.onFusedLocationProviderResult(FusedLocationProviderResult.SUCCESS); + } catch (RemoteException ignored) { + } + } + } catch (Exception e) { + Log.w(TAG, "Exception in updateLocationRequest", e); + if (data.fusedLocationProviderCallback != null) { + try { + Log.d(TAG, "Send internal error result"); + data.fusedLocationProviderCallback.onFusedLocationProviderResult(FusedLocationProviderResult.create(Status.INTERNAL_ERROR)); + } catch (RemoteException ignored) { } } } - if (data.fusedLocationProviderCallback != null) { - try { - data.fusedLocationProviderCallback.onFusedLocationProviderResult(FusedLocationProviderResult.SUCCESS); - } catch (RemoteException ignored) { - } - } - verifyCurrentRequests(); } public void setMockMode(boolean mockMode) { @@ -250,4 +281,13 @@ public class GoogleLocationManager implements LocationChangeListener { } } } + + public void dump(PrintWriter writer) { + if (gpsProvider != null) gpsProvider.dump(writer); + if (networkProvider != null) networkProvider.dump(writer); + writer.println(currentRequests.size() + " requests:"); + for (LocationRequestHelper request : currentRequests) { + writer.println(" " + request.id + " package=" + request.packageName + " interval=" + request.locationRequest.getInterval() + " smallestDisplacement=" + request.locationRequest.getSmallestDisplacement()); + } + } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java index af6a3153..bf6c9870 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerService.java @@ -27,11 +27,14 @@ import com.google.android.gms.common.internal.IGmsCallbacks; import org.microg.gms.BaseService; import org.microg.gms.common.GmsService; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class GoogleLocationManagerService extends BaseService { - private GoogleLocationManagerServiceImpl impl = new GoogleLocationManagerServiceImpl(this); + private GoogleLocationManagerServiceImpl impl = new GoogleLocationManagerServiceImpl(this, getLifecycle()); public GoogleLocationManagerService() { - super("GmsLocManagerSvc", GmsService.LOCATION_MANAGER, GmsService.GEODATA, GmsService.PLACE_DETECTION); + super("LocationManager", GmsService.LOCATION_MANAGER, GmsService.GEODATA, GmsService.PLACE_DETECTION); } @Override @@ -49,4 +52,9 @@ public class GoogleLocationManagerService extends BaseService { } }); } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + impl.getLocationManager().dump(writer); + } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java index b4315769..8169a4c7 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/GoogleLocationManagerServiceImpl.java @@ -20,12 +20,19 @@ import android.app.PendingIntent; import android.content.Context; import android.location.Location; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; + import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.api.internal.IStatusCallback; import com.google.android.gms.location.ActivityRecognitionResult; import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.GestureRequest; @@ -35,6 +42,7 @@ import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStates; +import com.google.android.gms.location.internal.DeviceOrientationRequestUpdateData; import com.google.android.gms.location.internal.IGeofencerCallbacks; import com.google.android.gms.location.internal.IGoogleLocationManagerService; import com.google.android.gms.location.internal.ISettingsCallbacks; @@ -58,23 +66,31 @@ import org.microg.gms.common.PackageUtils; import java.util.Arrays; import java.util.List; -public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerService.Stub { +public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerService.Stub implements LifecycleOwner { private static final String TAG = "GmsLocManagerSvcImpl"; private final Context context; + private final Lifecycle lifecycle; private GoogleLocationManager locationManager; - public GoogleLocationManagerServiceImpl(Context context) { + public GoogleLocationManagerServiceImpl(Context context, Lifecycle lifecycle) { this.context = context; + this.lifecycle = lifecycle; + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return lifecycle; } public void invokeOnceReady(Runnable runnable) { getLocationManager().invokeOnceReady(runnable); } - private GoogleLocationManager getLocationManager() { + public synchronized GoogleLocationManager getLocationManager() { if (locationManager == null) - locationManager = new GoogleLocationManager(context); + locationManager = new GoogleLocationManager(context, lifecycle); return locationManager; } @@ -123,18 +139,6 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ return null; } - @Override - public Status iglms65(PendingIntent pendingIntent) throws RemoteException { - Log.d(TAG, "iglms65"); - return null; - } - - @Override - public Status iglms66(PendingIntent pendingIntent) throws RemoteException { - Log.d(TAG, "iglms66"); - return null; - } - @Override public Status requestGestureUpdates(GestureRequest request, PendingIntent pendingIntent) throws RemoteException { Log.d(TAG, "requestGestureUpdates"); @@ -199,6 +203,11 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ getLocationManager().setMockLocation(mockLocation); } + @Override + public void injectLocation(Location mockLocation, int injectionType) throws RemoteException { + Log.d(TAG, "injectLocation[" + injectionType + "]: " + mockLocation); + } + @Override public void iglms14(LatLngBounds var1, int var2, PlaceFilter var3, PlacesParams var4, IPlacesCallbacks var5) throws RemoteException { @@ -250,13 +259,14 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ } @Override - public void iglms25(PlaceReport var1, PlacesParams var2) throws RemoteException { - Log.d(TAG, "iglms25: " + var1); + public Location getLastLocationWith(String s) throws RemoteException { + Log.d(TAG, "getLastLocationWith: " + s); + return getLastLocation(); } @Override - public void iglms26(Location var1, int var2) throws RemoteException { - Log.d(TAG, "iglms26: " + var1); + public void iglms25(PlaceReport var1, PlacesParams var2) throws RemoteException { + Log.d(TAG, "iglms25: " + var1); } @Override @@ -266,6 +276,11 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ return new LocationAvailability(); } + @Override + public void removeSleepSegmentUpdates(PendingIntent pendingIntent, IStatusCallback callback) throws RemoteException { + Log.d(TAG, "removeSleepSegmentUpdates"); + } + @Override public void iglms42(String var1, PlacesParams var2, IPlacesCallbacks var3) throws RemoteException { @@ -311,7 +326,29 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ public void requestLocationSettingsDialog(LocationSettingsRequest settingsRequest, ISettingsCallbacks callback, String packageName) throws RemoteException { Log.d(TAG, "requestLocationSettingsDialog: " + settingsRequest); PackageUtils.getAndCheckCallingPackage(context, packageName); - callback.onLocationSettingsResult(new LocationSettingsResult(new LocationSettingsStates(true, true, false, true, true, false), Status.SUCCESS)); + (new Handler(Looper.getMainLooper())).post(() -> { + try { + callback.onLocationSettingsResult(new LocationSettingsResult(new LocationSettingsStates(true, true, true, true, true, true), Status.SUCCESS)); + } catch (RemoteException e) { + Log.w(TAG, e); + } + }); + } + + @Override + public void removeActivityTransitionUpdates(PendingIntent pendingIntent, IStatusCallback callback) throws RemoteException { + Log.d(TAG, "removeActivityTransitionUpdates"); + } + + @Override + public void updateDeviceOrientationRequest(DeviceOrientationRequestUpdateData request) throws RemoteException { + Log.d(TAG, "updateDeviceOrientationRequest: " + request); + } + + @Override + public boolean setActivityRecognitionMode(int mode) throws RemoteException { + Log.d(TAG, "setActivityRecognitionMode: " + mode); + return false; } @Override @@ -352,6 +389,37 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ Log.d(TAG, "iglms58: " + var1); } + @Override + public void iglms65(PendingIntent pendingIntent, IStatusCallback callback) throws RemoteException { + Log.d(TAG, "iglms65"); + } + + @Override + public void iglms66(PendingIntent pendingIntent, IStatusCallback callback) throws RemoteException { + Log.d(TAG, "iglms66"); + } + + @Override + public void iglms68(PendingIntent pendingIntent, IStatusCallback callback) throws RemoteException { + Log.d(TAG, "iglms68"); + } + + @Override + public void iglms71(IStatusCallback callback) throws RemoteException { + Log.d(TAG, "iglms71"); + } + + @Override + public void iglms76(PendingIntent pendingIntent) throws RemoteException { + Log.d(TAG, "iglms76"); + } + + @Override + public int iglms78() throws RemoteException { + Log.d(TAG, "iglms78"); + return 0; + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (super.onTransact(code, data, reply, flags)) return true; diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java b/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java index d1991efb..6fdae8d6 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/LocationRequestHelper.java @@ -17,7 +17,6 @@ package org.microg.gms.location; import android.annotation.TargetApi; -import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; @@ -35,12 +34,9 @@ import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.internal.LocationRequestUpdateData; -import org.microg.gms.common.PackageUtils; - import java.util.Arrays; -import java.util.List; +import java.util.UUID; -import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; @@ -56,6 +52,7 @@ public class LocationRequestHelper { public ILocationListener listener; public PendingIntent pendingIntent; public ILocationCallback callback; + public String id = UUID.randomUUID().toString(); private Location lastReport; private int numReports = 0; @@ -117,10 +114,13 @@ public class LocationRequestHelper { if (location == null) return true; if (!isActive()) return false; if (lastReport != null) { + if (location.equals(lastReport)) { + return true; + } if (location.getTime() - lastReport.getTime() < locationRequest.getFastestInterval()) { return true; } - if (location.distanceTo(lastReport) < locationRequest.getSmallestDesplacement()) { + if (location.distanceTo(lastReport) < locationRequest.getSmallestDisplacement()) { return true; } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java index 98e1947a..cb4fd496 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/RealLocationProvider.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.os.Looper; import android.util.Log; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -44,7 +45,12 @@ public class RealLocationProvider { private LocationListener listener = new LocationListener() { @Override public void onLocationChanged(Location location) { - lastLocation = location; + lastLocation = new Location(location); + try { + lastLocation.getExtras().keySet(); // call to unparcel() + } catch (Exception e) { + // Sometimes we need to define the correct ClassLoader before unparcel(). Ignore those. + } changeListener.onLocationChanged(); } @@ -121,7 +127,7 @@ public class RealLocationProvider { StringBuilder sb = new StringBuilder(); for (LocationRequestHelper request : requests) { minTime = Math.min(request.locationRequest.getInterval(), minTime); - minDistance = Math.min(request.locationRequest.getSmallestDesplacement(), minDistance); + minDistance = Math.min(request.locationRequest.getSmallestDisplacement(), minDistance); if (sb.length() != 0) sb.append(", "); sb.append(request.packageName).append(":").append(request.locationRequest.getInterval()).append("ms"); } @@ -146,4 +152,16 @@ public class RealLocationProvider { connectedMinDistance = minDistance; } } + + public void dump(PrintWriter writer) { + if (writer != null) { + writer.println(name + " provider:"); + writer.println(" last location: " + lastLocation); + writer.println(" active: " + connected.get()); + if (connected.get()) { + writer.println(" interval: " + connectedMinTime); + writer.println(" distance: " + connectedMinDistance); + } + } + } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java b/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java index c091cbde..f369f84e 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java +++ b/play-services-location-core/src/main/java/org/microg/gms/location/ReportingServiceImpl.java @@ -17,6 +17,7 @@ package org.microg.gms.location; import android.accounts.Account; +import android.os.Parcel; import android.os.RemoteException; import android.util.Log; @@ -58,4 +59,15 @@ public class ReportingServiceImpl extends IReportingService.Stub { Log.d(TAG, "reportDeviceAtPlace"); return 0; } + + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + if (super.onTransact(code, data, reply, flags)) { + return true; + } + + Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags); + return false; + } } diff --git a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt index fa952dca..f4df00fa 100644 --- a/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt +++ b/play-services-location-core/src/main/java/org/microg/gms/location/UnifiedLocationProvider.kt @@ -2,32 +2,54 @@ package org.microg.gms.location import android.content.Context import android.location.Location +import android.os.Bundle import android.util.Log -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.microg.nlp.client.UnifiedLocationClient +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.microg.nlp.client.LocationClient +import org.microg.nlp.service.api.Constants +import org.microg.nlp.service.api.ILocationListener +import org.microg.nlp.service.api.LocationRequest +import java.io.PrintWriter +import java.lang.Exception import java.util.* -import java.util.concurrent.atomic.AtomicBoolean +import kotlin.collections.ArrayList -class UnifiedLocationProvider(context: Context?, changeListener: LocationChangeListener) { - private val client: UnifiedLocationClient - private var connectedMinTime: Long = 0 +class UnifiedLocationProvider(private val context: Context, private val changeListener: LocationChangeListener, private val lifecycle: Lifecycle): LifecycleOwner { + private val client: LocationClient = LocationClient(context, lifecycle) private var lastLocation: Location? = null - private val connected = AtomicBoolean(false) - private val changeListener: LocationChangeListener private val requests: MutableList = ArrayList() - private val listener: UnifiedLocationClient.LocationListener = object : UnifiedLocationClient.LocationListener { - override fun onLocation(location: Location) { - lastLocation = location - changeListener.onLocationChanged() + private val activeRequestIds = hashSetOf() + private val activeRequestMutex = Mutex(false) + private val listener: ILocationListener = object : ILocationListener.Stub() { + override fun onLocation(statusCode: Int, location: Location?) { + if (statusCode == Constants.STATUS_OK && location != null) { + lastLocation = Location(location) + try { + for (key in lastLocation?.extras?.keySet()?.toList().orEmpty()) { + if (key?.startsWith("org.microg.nlp.") == true) { + lastLocation?.extras?.remove(key) + } + } + } catch (e:Exception){ + // Sometimes we need to define the correct ClassLoader before unparcel(). Ignore those. + } + changeListener.onLocationChanged() + } } } private var ready = false private val invokeOnceReady = hashSetOf() + init { + updateLastLocation() + } + private fun updateLastLocation() { - GlobalScope.launch(Dispatchers.Main) { + lifecycleScope.launchWhenStarted { Log.d(TAG, "unified network: requesting last location") val lastLocation = client.getLastLocation() Log.d(TAG, "unified network: got last location: $lastLocation") @@ -78,42 +100,40 @@ class UnifiedLocationProvider(context: Context?, changeListener: LocationChangeL @Synchronized private fun updateConnection() { - if (connected.get() && requests.isEmpty()) { - Log.d(TAG, "unified network: no longer requesting location update") - client.removeLocationUpdates(listener) - connected.set(false) - } else if (requests.isNotEmpty()) { - var minTime = Long.MAX_VALUE - var maxUpdates = Int.MAX_VALUE - val sb = StringBuilder() - var opPackageName: String? = null - for (request in requests) { - if (request.locationRequest.interval < minTime) { - opPackageName = request.packageName - minTime = request.locationRequest.interval - maxUpdates = request.locationRequest.numUpdates + lifecycleScope.launchWhenStarted { + activeRequestMutex.withLock { + if (activeRequestIds.isNotEmpty() && requests.isEmpty()) { + Log.d(TAG, "unified network: no longer requesting location update") + for (id in activeRequestIds) { + client.cancelLocationRequestById(id) + } + activeRequestIds.clear() + } else if (requests.isNotEmpty()) { + val requests = ArrayList(requests).filter { it.isActive } + for (id in activeRequestIds.filter { id -> requests.none { it.id == id } }) { + client.cancelLocationRequestById(id) + } + for (request in requests.filter { it.id !in activeRequestIds }) { + client.updateLocationRequest(LocationRequest(listener, request.locationRequest.interval, request.locationRequest.numUpdates, request.id), Bundle().apply { + putString("packageName", request.packageName) + putString("source", "GoogleLocationManager") + }) + activeRequestIds.add(request.id) + } } - if (sb.isNotEmpty()) sb.append(", ") - sb.append("${request.packageName}:${request.locationRequest.interval}ms") } - client.opPackageName = opPackageName - if (minTime <= 0) { - client.forceNextUpdate = true - } - Log.d(TAG, "unified network: requesting location updates with interval ${minTime}ms ($sb)") - client.requestLocationUpdates(listener, minTime, maxUpdates) - connected.set(true) - connectedMinTime = minTime } } + override fun getLifecycle(): Lifecycle = lifecycle + + fun dump(writer: PrintWriter) { + writer.println("network provider (via direct client):") + writer.println(" last location: $lastLocation") + writer.println(" ready: $ready") + } + companion object { const val TAG = "GmsLocProviderU" } - - init { - client = UnifiedLocationClient[context!!] - this.changeListener = changeListener - updateLastLocation() - } } diff --git a/play-services-location/src/main/java/org/microg/gms/location/NativeLocationClientImpl.java b/play-services-location/src/main/java/org/microg/gms/location/NativeLocationClientImpl.java index 17a017d9..78e0cd50 100644 --- a/play-services-location/src/main/java/org/microg/gms/location/NativeLocationClientImpl.java +++ b/play-services-location/src/main/java/org/microg/gms/location/NativeLocationClientImpl.java @@ -135,7 +135,7 @@ public class NativeLocationClientImpl { i.putExtras(bundle); pendingCount.put(pendingIntent, request.getNumUpdates()); nativePendingMap.put(pendingIntent, PendingIntent.getActivity(context, 0, i, 0)); - locationManager.requestLocationUpdates(request.getInterval(), request.getSmallestDesplacement(), + locationManager.requestLocationUpdates(request.getInterval(), request.getSmallestDisplacement(), makeNativeCriteria(request), nativePendingMap.get(pendingIntent)); } @@ -147,7 +147,7 @@ public class NativeLocationClientImpl { } nativeListenerMap.put(listener, new NativeListener(listener, request.getNumUpdates())); locationManager.requestLocationUpdates(request.getInterval(), - request.getSmallestDesplacement(), makeNativeCriteria(request), + request.getSmallestDisplacement(), makeNativeCriteria(request), nativeListenerMap.get(listener), looper); } From 050afb8f8765db8fbfd84244c9589a7d859028f7 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:47:01 +0100 Subject: [PATCH 39/63] Update base --- .../main/java/org/microg/gms/BaseService.java | 10 ++++- .../org/microg/gms/common/PackageUtils.java | 37 +++++++++++++++++++ .../org/microg/gms/utils/BinderUtils.kt | 9 +++-- .../microg/gms/utils/PackageManagerWrapper.kt | 2 +- .../java/org/microg/gms/common/GmsClient.java | 2 +- .../gms/common/api/GoogleApiManager.java | 1 + .../google/android/gms/common/api/Status.java | 6 +-- .../common/internal/GetServiceRequest.java | 4 ++ .../org/microg/gms/common/GmsService.java | 22 ++++++++++- 9 files changed, 82 insertions(+), 11 deletions(-) diff --git a/play-services-base-core/src/main/java/org/microg/gms/BaseService.java b/play-services-base-core/src/main/java/org/microg/gms/BaseService.java index df1b7288..3941e508 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/BaseService.java +++ b/play-services-base-core/src/main/java/org/microg/gms/BaseService.java @@ -29,16 +29,19 @@ import com.google.android.gms.common.internal.IGmsServiceBroker; import org.microg.gms.common.GmsService; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Arrays; import java.util.EnumSet; public abstract class BaseService extends LifecycleService { private final IGmsServiceBroker broker; + private final EnumSet services; protected final String TAG; public BaseService(String tag, GmsService supportedService, GmsService... supportedServices) { this.TAG = tag; - EnumSet services = EnumSet.of(supportedService); + services = EnumSet.of(supportedService); services.addAll(Arrays.asList(supportedServices)); broker = new AbstractGmsServiceBroker(services) { @Override @@ -61,5 +64,10 @@ public abstract class BaseService extends LifecycleService { return broker.asBinder(); } + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println(TAG + " providing services " + services.toString()); + } + public abstract void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException; } diff --git a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java index 08a0db17..073a0d8c 100644 --- a/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java +++ b/play-services-base-core/src/main/java/org/microg/gms/common/PackageUtils.java @@ -137,6 +137,30 @@ public class PackageUtils { return null; } + @Nullable + public static byte[] firstSignatureDigestBytes(Context context, String packageName) { + return firstSignatureDigestBytes(context.getPackageManager(), packageName); + } + + @Nullable + public static byte[] firstSignatureDigestBytes(PackageManager packageManager, String packageName) { + final PackageInfo info; + try { + info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + if (info != null && info.signatures != null && info.signatures.length > 0) { + for (Signature sig : info.signatures) { + byte[] digest = sha1bytes(sig.toByteArray()); + if (digest != null) { + return digest; + } + } + } + return null; + } + @Nullable public static String getCallingPackage(Context context) { int callingUid = Binder.getCallingUid(), callingPid = Binder.getCallingPid(); @@ -311,6 +335,19 @@ public class PackageUtils { return null; } + public static byte[] sha1bytes(byte[] bytes) { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA1"); + } catch (final NoSuchAlgorithmException e) { + return null; + } + if (md != null) { + return md.digest(bytes); + } + return null; + } + public static int versionCode(Context context, String packageName) { try { return context.getPackageManager().getPackageInfo(packageName, 0).versionCode; diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt index f00c97fb..79e8ff8f 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/BinderUtils.kt @@ -6,16 +6,19 @@ package org.microg.gms.utils import android.os.Binder +import android.os.IBinder import android.os.Parcel import android.util.Log -fun warnOnTransactionIssues(tag: String, code: Int, reply: Parcel?, flags: Int, base: () -> Boolean): Boolean { +private const val TAG = "BinderUtils" + +fun IBinder.warnOnTransactionIssues(code: Int, reply: Parcel?, flags: Int, base: () -> Boolean): Boolean { if (base.invoke()) { if ((flags and Binder.FLAG_ONEWAY) > 0 && (reply?.dataSize() ?: 0) > 0) { - Log.w(tag, "onTransact[$code] is oneway, but returned data") + Log.w(TAG, "Method $code in $interfaceDescriptor is oneway, but returned data") } return true } - Log.w(tag, "onTransact[$code] is not processed.") + Log.w(TAG, "Unknown method $code in $interfaceDescriptor, skipping") return (flags and Binder.FLAG_ONEWAY) > 0 // Don't return false on oneway transaction to suppress warning } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt index 4a958a57..58e87c43 100644 --- a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/PackageManagerWrapper.kt @@ -62,7 +62,7 @@ open class PackageManagerWrapper(private val wrapped: PackageManager) : PackageM return wrapped.getPermissionInfo(permName, flags) } - override fun queryPermissionsByGroup(permissionGroup: String, flags: Int): MutableList { + override fun queryPermissionsByGroup(permissionGroup: String?, flags: Int): MutableList { return wrapped.queryPermissionsByGroup(permissionGroup, flags) } diff --git a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java index dec9c8ca..a03b2c3b 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java +++ b/play-services-base/src/main/java/org/microg/gms/common/GmsClient.java @@ -111,7 +111,7 @@ public abstract class GmsClient implements ApiClient { @Override public synchronized boolean isConnected() { - return state == ConnectionState.CONNECTED || state == ConnectionState.PSEUDO_CONNECTED; + return (state == ConnectionState.CONNECTED && serviceInterface != null && serviceInterface.asBinder().isBinderAlive() ) || state == ConnectionState.PSEUDO_CONNECTED; } @Override diff --git a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java index 1662ca31..8ceae6f0 100644 --- a/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java +++ b/play-services-base/src/main/java/org/microg/gms/common/api/GoogleApiManager.java @@ -7,6 +7,7 @@ package org.microg.gms.common.api; import android.content.Context; import android.os.Bundle; +import android.os.DeadObjectException; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java b/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java index 6a80ff39..d5a06814 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/api/Status.java @@ -32,11 +32,11 @@ import org.microg.safeparcel.SafeParceled; @PublicApi public final class Status extends AutoSafeParcelable implements Result { @PublicApi(exclude = true) - public static final Status INTERNAL_ERROR = new Status(CommonStatusCodes.INTERNAL_ERROR); + public static final Status INTERNAL_ERROR = new Status(CommonStatusCodes.INTERNAL_ERROR, "Internal error"); @PublicApi(exclude = true) - public static final Status CANCELED = new Status(CommonStatusCodes.CANCELED); + public static final Status CANCELED = new Status(CommonStatusCodes.CANCELED, "Cancelled"); @PublicApi(exclude = true) - public static final Status SUCCESS = new Status(CommonStatusCodes.SUCCESS); + public static final Status SUCCESS = new Status(CommonStatusCodes.SUCCESS, "Success"); @SafeParceled(1000) private int versionCode = 1; diff --git a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java index fbd84dba..9b7c594e 100644 --- a/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java +++ b/play-services-basement/src/main/java/com/google/android/gms/common/internal/GetServiceRequest.java @@ -57,6 +57,10 @@ public class GetServiceRequest extends AutoSafeParcelable { private boolean field12; @Field(13) private int field13; + @Field(14) + private boolean field14; + @Field(15) + private String field15; private GetServiceRequest() { serviceId = -1; diff --git a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java index bd289c70..108eb625 100644 --- a/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java +++ b/play-services-basement/src/main/java/org/microg/gms/common/GmsService.java @@ -33,7 +33,7 @@ public enum GmsService { ADDRESS(12, "com.google.android.gms.identity.service.BIND"), CAR(13, "com.google.android.gms.car.service.START"), WEARABLE(14, "com.google.android.gms.wearable.BIND"), - AUTH(16, "com.google.android.gms.auth.service.START"), + AUTH_PROXY(16, "com.google.android.gms.auth.service.START"), FITNESS(17, "com.google.android.gms.fitness.GoogleFitnessService.START"), REMINDERS(18, "com.google.android.gms.reminders.service.START"), LIGHTWEIGHT_INDEX(19, "com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE"), @@ -103,9 +103,12 @@ public enum GmsService { APP_INDEXING(113), GASS(116, "com.google.android.gms.gass.START"), WORK_ACCOUNT(120), + INSTANT_APPS(121, "com.google.android.gms.instantapps.START"), CAST_FIRSTPATY(122, "com.google.android.gms.cast.firstparty.START"), AD_CACHE(123, "com.google.android.gms.ads.service.CACHE"), + CRYPT_AUTH(129, "com.google.android.gms.auth.cryptauth.cryptauthservice.START"), DYNAMIC_LINKS(131, "com.google.firebase.dynamiclinks.service.START"), + FONTS(132, "com.google.android.gms.fonts.service.START"), ROMANESCO(135, "com.google.android.gms.romanesco.service.START"), TRAINER(139, "com.google.android.gms.learning.trainer.START"), FIDO2_REGULAR(148, "com.google.android.gms.fido.fido2.regular.START"), @@ -116,14 +119,19 @@ public enum GmsService { AUDIT(154, "com.google.android.gms.audit.service.START"), SYSTEM_UPDATE(157, "com.google.android.gms.update.START_API_SERVICE"), USER_LOCATION(163, "com.google.android.gms.userlocation.service.START"), + AD_HTTP(166, "com.google.android.gms.ads.service.HTTP"), LANGUAGE_PROFILE(167, "com.google.android.gms.languageprofile.service.START"), MDNS(168, "com.google.android.gms.mdns.service.START"), FIDO2_ZEROPARTY(180, "com.google.android.gms.fido.fido2.zeroparty.START"), G1_RESTORE(181, "com.google.android.gms.backup.G1_RESTORE"), G1_BACKUP(182, "com.google.android.gms.backup.G1_BACKUP"), + PAYSE(188, "com.google.android.gms.payse.service.BIND"), + RCS(189, "com.google.android.gms.rcs.START"), CARRIER_AUTH(191, "com.google.android.gms.carrierauth.service.START"), SYSTEM_UPDATE_SINGLE_UESR(192, "com.google.android.gms.update.START_SINGLE_USER_API_SERVICE"), APP_USAGE(193, "com.google.android.gms.appusage.service.START"), + NEARBY_SHARING_2(194, "com.google.android.gms.nearby.sharing.START_SERVICE"), + AD_CONSENT_LOOKUP(195, "com.google.android.gms.ads.service.CONSENT_LOOKUP"), PHONE_INTERNAL(197, "com.google.android.gms.auth.api.phone.service.InternalService.START"), PAY(198, "com.google.android.gms.pay.service.BIND"), ASTERISM(199, "com.google.android.gms.asterism.service.START"), @@ -137,12 +145,22 @@ public enum GmsService { SCHEDULER(218, "com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE"), AUTHORIZATION(219, "com.google.android.gms.auth.api.identity.service.authorization.START"), FACS_SYNC(220, "com.google.android.gms.facs.internal.service.START"), - CONFIG_SYNC(221, "com.google.android.gms.auth.config.service.START"), + AUTH_CONFIG_SYNC(221, "com.google.android.gms.auth.config.service.START"), CREDENTIAL_SAVING(223, "com.google.android.gms.auth.api.identity.service.credentialsaving.START"), GOOGLE_AUTH(224, "com.google.android.gms.auth.account.authapi.START"), ENTERPRISE_LOADER(225, "com.google.android.gms.enterprise.loader.service.START"), THUNDERBIRD(226, "com.google.android.gms.thunderbird.service.START"), NEARBY_EXPOSURE(236, "com.google.android.gms.nearby.exposurenotification.START"), + GMS_COMPLIANCE(257, "com.google.android.gms.gmscompliance.service.START"), + FIDO_SOURCE_DEVICE(262, "com.google.android.gms.fido.sourcedevice.service.START"), + FAST_PAIR(265, "com.google.android.gms.nearby.fastpair.START"), + MATCHSTICK_LIGHTER(268, "com.google.android.gms.matchstick.lighter.service.START"), + FIDO_TARGET_DEVICE_INTERNAL(269, "com.google.android.gms.fido.targetdevice.internal_service.START"), + TELEMETRY(270, "com.google.android.gms.common.telemetry.service.START"), + SECOND_DEVICE_AUTH(275, "com.google.android.gms.setup.auth.SecondDeviceAuth.START"), + LOCATION_SHARING_REPORTER(277, "com.google.android.gms.locationsharingreporter.service.START"), + OCR(279, "com.google.android.gms.ocr.service.START"), + OCR_INTERNAL(281, "com.google.android.gms.ocr.service.internal.START"), ; public int SERVICE_ID; From e3b042ccd7e6699b44c608a2f5e22d6a58bacba0 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:47:24 +0100 Subject: [PATCH 40/63] Update GCM/IID client code --- .../android/gms/gcm/GoogleCloudMessaging.java | 5 +- .../google/android/gms/iid/InstanceID.java | 15 ++++-- .../org/microg/gms/iid/InstanceIdStore.java | 48 ++++++++++++------- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java index 06546d7b..5fc53346 100644 --- a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java @@ -30,13 +30,10 @@ import org.microg.gms.gcm.GcmConstants; import java.io.IOException; -import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; import static org.microg.gms.gcm.GcmConstants.EXTRA_DELAY; -import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR; import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID; import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_TYPE; -import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID; import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER; import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER_LEGACY; import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_FROM; @@ -317,6 +314,6 @@ public class GoogleCloudMessaging { if (i > 0) { to = to.substring(0, i); } - return InstanceID.getInstance(context).getStore().get("", to, INSTANCE_ID_SCOPE); + return InstanceID.getInstance(context).getStore().getToken("", to, INSTANCE_ID_SCOPE); } } diff --git a/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java b/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java index 8a4dbefa..327d8b1c 100644 --- a/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java +++ b/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java @@ -154,7 +154,7 @@ public class InstanceID { */ public long getCreationTime() { if (creationTime == 0) { - String s = storeInstance.get(subtype, "cre"); + String s = storeInstance.getSecret(subtype, "cre"); if (s != null) { creationTime = Long.parseLong(s); } @@ -211,7 +211,14 @@ public class InstanceID { public String getToken(String authorizedEntity, String scope, Bundle extras) throws IOException { if (Looper.getMainLooper() == Looper.myLooper()) throw new IOException(ERROR_MAIN_THREAD); - throw new UnsupportedOperationException(); + long tokenTimestamp = storeInstance.getTokenTimestamp(subtype, authorizedEntity, scope); + if (tokenTimestamp > System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L) { + String token = storeInstance.getToken(subtype, authorizedEntity, scope); + if (token != null) return token; + } + String token = requestToken(authorizedEntity, scope, extras); + storeInstance.putToken(subtype, authorizedEntity, scope, token); + return token; } /** @@ -252,7 +259,7 @@ public class InstanceID { rsaGenerator.initialize(RSA_KEY_SIZE); keyPair = rsaGenerator.generateKeyPair(); creationTime = System.currentTimeMillis(); - storeInstance.put(subtype, keyPair, creationTime); + storeInstance.putKeyPair(subtype, keyPair, creationTime); } catch (NoSuchAlgorithmException e) { Log.w(TAG, e); } @@ -272,4 +279,4 @@ public class InstanceID { return null; } } -} \ No newline at end of file +} diff --git a/play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdStore.java b/play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdStore.java index 65778936..94225dab 100644 --- a/play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdStore.java +++ b/play-services-iid/src/main/java/org/microg/gms/iid/InstanceIdStore.java @@ -38,21 +38,29 @@ public class InstanceIdStore { this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); } - public synchronized String get(String key) { + public synchronized String getString(String key) { return sharedPreferences.getString(key, null); } - public String get(String subtype, String key) { - return get(subtype + "|S|" + key); + public synchronized long getLong(String key) { + return sharedPreferences.getLong(key, -1); } - public String get(String subtype, String authorizedEntity, String scope) { - return get(subtype + "|T|" + authorizedEntity + "|" + scope); + public String getSecret(String subtype, String key) { + return getString(subtype + "|S|" + key); + } + + public String getToken(String subtype, String authorizedEntity, String scope) { + return getString(subtype + "|T|" + authorizedEntity + "|" + scope); + } + + public long getTokenTimestamp(String subtype, String authorizedEntity, String scope) { + return getLong(subtype + "|T-timestamp|" + authorizedEntity + "|" + scope); } public KeyPair getKeyPair(String subtype) { - String pub = get(subtype, "|P|"); - String priv = get(subtype, "|K|"); + String pub = getSecret(subtype, "|P|"); + String priv = getSecret(subtype, "|K|"); if (pub == null || priv == null) { return null; } @@ -67,24 +75,31 @@ public class InstanceIdStore { } } - public synchronized void put(String key, String value) { + public synchronized void putString(String key, String value) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString(key, value); editor.apply(); } - public void put(String subtype, String key, String value) { - put(subtype + "|S|" + key, value); + public synchronized void putLong(String key, long value) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putLong(key, value); + editor.apply(); } - public void put(String subtype, String authorizedEntity, String scope, String value) { - put(subtype + "|T|" + authorizedEntity + "|" + scope, value); + public void putSecret(String subtype, String key, String value) { + putString(subtype + "|S|" + key, value); } - public synchronized void put(String subtype, KeyPair keyPair, long timestamp) { - put(subtype, "|P|", Base64.encodeToString(keyPair.getPublic().getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING)); - put(subtype, "|K|", Base64.encodeToString(keyPair.getPrivate().getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING)); - put(subtype, "cre", Long.toString(timestamp)); + public void putToken(String subtype, String authorizedEntity, String scope, String token) { + putString(subtype + "|T|" + authorizedEntity + "|" + scope, token); + putLong(subtype + "|T-timestamp|" + authorizedEntity + "|" + scope, System.currentTimeMillis()); + } + + public synchronized void putKeyPair(String subtype, KeyPair keyPair, long timestamp) { + putSecret(subtype, "|P|", Base64.encodeToString(keyPair.getPublic().getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING)); + putSecret(subtype, "|K|", Base64.encodeToString(keyPair.getPrivate().getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING)); + putSecret(subtype, "cre", Long.toString(timestamp)); } public synchronized void delete() { @@ -106,6 +121,7 @@ public class InstanceIdStore { public synchronized void delete(String subtype, String authorizedEntity, String scope) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove(subtype + "|T|" + authorizedEntity + "|" + scope); + editor.remove(subtype + "|T-timestamp|" + authorizedEntity + "|" + scope); editor.apply(); } } From fd8ce71a836c8bf8ac9786d6ce0f58807a4a23c5 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:49:35 +0100 Subject: [PATCH 41/63] Add initial UI for new features --- LICENSES/CC0-1.0.txt | 121 ++++++++++++++++++ .../src/main/res/drawable/ic_radio.xml | 16 +++ .../main/res/drawable/ic_radio_checked.xml | 16 +++ .../main/res/drawable/ic_radio_unchecked.xml | 16 +++ play-services-core/build.gradle | 9 +- .../gms/ui/SafetyNetAdvancedFragment.java | 108 ++++++++-------- .../gms/ui/SafetyNetAdvancedFragment.kt | 12 ++ .../org/microg/gms/ui/SafetyNetFragment.kt | 21 ++- .../gms/ui/SafetyNetPreferencesFragment.kt | 105 +++++++++++++++ .../src/main/res/drawable/ic_circle_check.xml | 16 +++ .../src/main/res/drawable/ic_circle_error.xml | 17 +++ .../main/res/drawable/ic_circle_pending.xml | 19 +++ .../src/main/res/drawable/ic_circle_warn.xml | 16 +++ .../layout/safety_net_advanced_fragment.xml | 17 +++ .../src/main/res/values-be/strings.xml | 18 +-- .../src/main/res/values-de/strings.xml | 18 +-- .../src/main/res/values-es/strings.xml | 18 +-- .../src/main/res/values-fr/strings.xml | 17 +-- .../src/main/res/values-it/strings.xml | 18 +-- .../src/main/res/values-ja/strings.xml | 18 +-- .../src/main/res/values-pl/strings.xml | 16 +-- .../src/main/res/values-ru/strings.xml | 18 +-- .../src/main/res/values-uk/strings.xml | 16 +-- .../src/main/res/values-zh-rTW/strings.xml | 13 +- .../src/main/res/values/strings.xml | 42 ++---- .../xml/preferences_device_registration.xml | 16 ++- .../src/main/res/xml/preferences_gcm.xml | 32 ----- .../xml/preferences_push_notifications.xml | 2 +- .../main/res/xml/preferences_safetynet.xml | 29 +++-- .../res/xml/preferences_snet_advanced.xml | 33 ++--- .../gms/ui/NearbyPreferencesIntegration.kt | 3 +- play-services-droidguard-core-ui/build.gradle | 53 ++++++++ .../src/main/AndroidManifest.xml | 11 ++ .../core/ui/ContainedEditTextPreference.kt | 69 ++++++++++ .../core/ui/DroidGuardPreferencesFragment.kt | 53 ++++++++ .../res/layout/preference_edit_widget.xml | 20 +++ .../preference_material_with_widget_below.xml | 75 +++++++++++ .../src/main/res/values/strings.xml | 12 ++ .../main/res/xml/preferences_droidguard.xml | 15 +++ settings.gradle | 4 + 40 files changed, 838 insertions(+), 310 deletions(-) create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 play-services-base-core-ui/src/main/res/drawable/ic_radio.xml create mode 100644 play-services-base-core-ui/src/main/res/drawable/ic_radio_checked.xml create mode 100644 play-services-base-core-ui/src/main/res/drawable/ic_radio_unchecked.xml create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAdvancedFragment.kt create mode 100644 play-services-core/src/main/res/drawable/ic_circle_check.xml create mode 100644 play-services-core/src/main/res/drawable/ic_circle_error.xml create mode 100644 play-services-core/src/main/res/drawable/ic_circle_pending.xml create mode 100644 play-services-core/src/main/res/drawable/ic_circle_warn.xml create mode 100644 play-services-core/src/main/res/layout/safety_net_advanced_fragment.xml delete mode 100644 play-services-core/src/main/res/xml/preferences_gcm.xml create mode 100644 play-services-droidguard-core-ui/build.gradle create mode 100644 play-services-droidguard-core-ui/src/main/AndroidManifest.xml create mode 100644 play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/ContainedEditTextPreference.kt create mode 100644 play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/DroidGuardPreferencesFragment.kt create mode 100644 play-services-droidguard-core-ui/src/main/res/layout/preference_edit_widget.xml create mode 100644 play-services-droidguard-core-ui/src/main/res/layout/preference_material_with_widget_below.xml create mode 100644 play-services-droidguard-core-ui/src/main/res/values/strings.xml create mode 100644 play-services-droidguard-core-ui/src/main/res/xml/preferences_droidguard.xml diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/play-services-base-core-ui/src/main/res/drawable/ic_radio.xml b/play-services-base-core-ui/src/main/res/drawable/ic_radio.xml new file mode 100644 index 00000000..689b57ad --- /dev/null +++ b/play-services-base-core-ui/src/main/res/drawable/ic_radio.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/play-services-base-core-ui/src/main/res/drawable/ic_radio_checked.xml b/play-services-base-core-ui/src/main/res/drawable/ic_radio_checked.xml new file mode 100644 index 00000000..8aad3213 --- /dev/null +++ b/play-services-base-core-ui/src/main/res/drawable/ic_radio_checked.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-base-core-ui/src/main/res/drawable/ic_radio_unchecked.xml b/play-services-base-core-ui/src/main/res/drawable/ic_radio_unchecked.xml new file mode 100644 index 00000000..2b79fb0f --- /dev/null +++ b/play-services-base-core-ui/src/main/res/drawable/ic_radio_unchecked.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 1209c603..05d07ce5 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -44,11 +44,12 @@ dependencies { implementation project(':play-services-base-core-ui') implementation project(':play-services-conscrypt-provider-core') implementation project(':play-services-cronet-core') - implementation project(':play-services-droidguard') // TODO: Move to play-services-safetynet-core once we have it implementation project(':play-services-droidguard-core') + implementation project(':play-services-droidguard-core-ui') implementation project(':play-services-location-core') withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') + implementation project(':play-services-safetynet-core') implementation project(':play-services-tapandpay-core') implementation project(':play-services-vision-core') @@ -58,7 +59,11 @@ dependencies { implementation project(':play-services-cast-api') implementation project(':play-services-wearable') implementation "org.microg:wearable:$wearableVersion" - implementation "org.microg.gms:remote-droid-guard:$remoteDroidGuardVersion" + + runtimeOnly "org.microg.nlp:geocode-v1:$nlpVersion" + runtimeOnly "org.microg.nlp:location-v2:$nlpVersion" + runtimeOnly "org.microg.nlp:location-v3:$nlpVersion" + implementation "org.microg.nlp:ui:$nlpVersion" withMapboxImplementation project(':play-services-maps-core-mapbox') withVtmImplementation project(':play-services-maps-core-vtm') diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SafetyNetAdvancedFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/SafetyNetAdvancedFragment.java index 97111a3b..906c1c8e 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/SafetyNetAdvancedFragment.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/SafetyNetAdvancedFragment.java @@ -28,58 +28,58 @@ import org.microg.tools.ui.AbstractSettingsActivity; import org.microg.tools.ui.RadioButtonPreference; import org.microg.tools.ui.ResourceSettingsFragment; -import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_OFFICIAL; -import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_SELF_SIGNED; -import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_THIRD_PARTY; +//import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_OFFICIAL; +//import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_SELF_SIGNED; +//import static org.microg.gms.safetynet.SafetyNetPrefs.PREF_SNET_THIRD_PARTY; -public class SafetyNetAdvancedFragment extends ResourceSettingsFragment { - - public SafetyNetAdvancedFragment() { - preferencesResource = R.xml.preferences_snet_advanced; - } - - private RadioButtonPreference radioOfficial; - private RadioButtonPreference radioSelfSigned; - private RadioButtonPreference radioThirdParty; - - @Override - public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { - super.onCreatePreferences(savedInstanceState, rootKey); - - radioOfficial = (RadioButtonPreference) findPreference(PREF_SNET_OFFICIAL); - radioSelfSigned = (RadioButtonPreference) findPreference(PREF_SNET_SELF_SIGNED); - radioThirdParty = (RadioButtonPreference) findPreference(PREF_SNET_THIRD_PARTY); - } - - @Override - public boolean onPreferenceTreeClick(Preference preference) { - if (preference == radioOfficial) { - radioOfficial.setChecked(true); - radioSelfSigned.setChecked(false); - radioThirdParty.setChecked(false); - return true; - } else if (preference == radioSelfSigned) { - radioOfficial.setChecked(false); - radioSelfSigned.setChecked(true); - radioThirdParty.setChecked(false); - return true; - } else if (preference == radioThirdParty) { - radioOfficial.setChecked(false); - radioSelfSigned.setChecked(false); - radioThirdParty.setChecked(true); - return true; - } - return super.onPreferenceTreeClick(preference); - } - - public static class AsActivity extends AbstractSettingsActivity { - public AsActivity() { - showHomeAsUp = true; - } - - @Override - protected Fragment getFragment() { - return new SafetyNetAdvancedFragment(); - } - } -} +//public class SafetyNetAdvancedFragment extends ResourceSettingsFragment { +// +// public SafetyNetAdvancedFragment() { +// preferencesResource = R.xml.preferences_snet_advanced; +// } +// +// private RadioButtonPreference radioOfficial; +// private RadioButtonPreference radioSelfSigned; +// private RadioButtonPreference radioThirdParty; +// +// @Override +// public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { +// super.onCreatePreferences(savedInstanceState, rootKey); +// +// radioOfficial = (RadioButtonPreference) findPreference(PREF_SNET_OFFICIAL); +// radioSelfSigned = (RadioButtonPreference) findPreference(PREF_SNET_SELF_SIGNED); +// radioThirdParty = (RadioButtonPreference) findPreference(PREF_SNET_THIRD_PARTY); +// } +// +// @Override +// public boolean onPreferenceTreeClick(Preference preference) { +// if (preference == radioOfficial) { +// radioOfficial.setChecked(true); +// radioSelfSigned.setChecked(false); +// radioThirdParty.setChecked(false); +// return true; +// } else if (preference == radioSelfSigned) { +// radioOfficial.setChecked(false); +// radioSelfSigned.setChecked(true); +// radioThirdParty.setChecked(false); +// return true; +// } else if (preference == radioThirdParty) { +// radioOfficial.setChecked(false); +// radioSelfSigned.setChecked(false); +// radioThirdParty.setChecked(true); +// return true; +// } +// return super.onPreferenceTreeClick(preference); +// } +// +// public static class AsActivity extends AbstractSettingsActivity { +// public AsActivity() { +// showHomeAsUp = true; +// } +// +// @Override +// protected Fragment getFragment() { +// return new SafetyNetAdvancedFragment(); +// } +// } +//} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAdvancedFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAdvancedFragment.kt new file mode 100644 index 00000000..668a6314 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetAdvancedFragment.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui + +import androidx.fragment.app.Fragment +import com.google.android.gms.R + +class SafetyNetAdvancedFragment : Fragment(R.layout.safety_net_advanced_fragment) { +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt index bae598e8..88656d7c 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetFragment.kt @@ -12,10 +12,9 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import com.google.android.gms.R import com.google.android.gms.databinding.SafetyNetFragmentBinding -import org.microg.gms.checkin.getCheckinServiceInfo -import org.microg.gms.safetynet.ServiceInfo -import org.microg.gms.safetynet.getSafetyNetServiceInfo -import org.microg.gms.safetynet.setSafetyNetServiceConfiguration +import org.microg.gms.checkin.CheckinPrefs +import org.microg.gms.droidguard.core.DroidGuardPreferences +import org.microg.gms.safetynet.SafetyNetPreferences class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { @@ -34,22 +33,22 @@ class SafetyNetFragment : Fragment(R.layout.safety_net_fragment) { fun setEnabled(newStatus: Boolean) { val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - val info = getSafetyNetServiceInfo(appContext) - val newConfiguration = info.configuration.copy(enabled = newStatus) - displayServiceInfo(setSafetyNetServiceConfiguration(appContext, newConfiguration)) + SafetyNetPreferences.setEnabled(appContext, newStatus) + DroidGuardPreferences.setEnabled(appContext, newStatus) + displayServiceInfo() } } - fun displayServiceInfo(serviceInfo: ServiceInfo) { - binding.safetynetEnabled = serviceInfo.configuration.enabled + fun displayServiceInfo() { + binding.safetynetEnabled = SafetyNetPreferences.isEnabled(requireContext()) && DroidGuardPreferences.isEnabled(requireContext()) } override fun onResume() { super.onResume() val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { - binding.checkinEnabled = getCheckinServiceInfo(appContext).configuration.enabled - displayServiceInfo(getSafetyNetServiceInfo(appContext)) + binding.checkinEnabled = CheckinPrefs.isEnabled(appContext) + displayServiceInfo() } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt index 2a4b4ab2..0643ce06 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt @@ -6,12 +6,117 @@ package org.microg.gms.ui import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Base64 +import android.util.Log +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R +import com.google.android.gms.common.api.Status +import com.google.android.gms.safetynet.AttestationData +import com.google.android.gms.safetynet.RecaptchaResultData +import com.google.android.gms.safetynet.internal.ISafetyNetCallbacks +import org.json.JSONException +import org.json.JSONObject +import org.microg.gms.safetynet.SafetyNetClientService +import org.microg.gms.safetynet.SafetyNetClientServiceImpl +import kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { + private lateinit var runAttest: Preference + private lateinit var runReCaptcha: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet) } + + override fun onBindPreferences() { + runAttest = preferenceScreen.findPreference("pref_snet_run_attest") ?: runAttest + runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha + + // TODO: Use SafetyNet client library once ready + runAttest.setOnPreferenceClickListener { + val context = context ?: return@setOnPreferenceClickListener false + runAttest.setIcon(R.drawable.ic_circle_pending) + runAttest.setSummary(R.string.pref_test_summary_running) + val handler = Handler(Looper.myLooper()!!) + SafetyNetClientServiceImpl(context, "com.scottyab.safetynet.sample", lifecycle).attestWithApiKey(object : ISafetyNetCallbacks.Default() { + override fun onAttestationData(status: Status?, attestationData: AttestationData?) { + handler.post { + if (status?.isSuccess == true) { + if (attestationData?.jwsResult == null) { + runAttest.setIcon(R.drawable.ic_circle_warn) + runAttest.summary = context.getString(R.string.pref_test_summary_failed, "No result") + } else { + val (_, payload, _) = try { + attestationData.jwsResult.split(".") + } catch (e: Exception) { + runAttest.setIcon(R.drawable.ic_circle_error) + runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JWS") + return@post + } + val (basicIntegrity, ctsProfileMatch, advice) = try { + JSONObject(Base64.decode(payload, Base64.URL_SAFE).decodeToString()).let { + Triple(it.optBoolean("basicIntegrity", false), it.optBoolean("ctsProfileMatch", false), it.optString("advice", "")) + } + } catch (e: Exception) { + Log.w(TAG, e) + runAttest.setIcon(R.drawable.ic_circle_error) + runAttest.summary = context.getString(R.string.pref_test_summary_failed, "Invalid JSON") + return@post + } + val adviceText = if (advice == "") "" else "\n" + advice.split(",").map { + when (it) { + "LOCK_BOOTLOADER" -> "Bootloader is not locked" + "RESTORE_TO_FACTORY_ROM" -> "ROM is not clean" + else -> it + } + }.joinToString("\n") + when { + basicIntegrity && ctsProfileMatch -> { + runAttest.setIcon(R.drawable.ic_circle_check) + runAttest.setSummary(R.string.pref_test_summary_passed) + } + basicIntegrity -> { + runAttest.setIcon(R.drawable.ic_circle_warn) + runAttest.summary = context.getString(R.string.pref_test_summary_warn, "CTS profile does not match$adviceText") + } + else -> { + runAttest.setIcon(R.drawable.ic_circle_error) + runAttest.summary = context.getString(R.string.pref_test_summary_failed, "integrity check failed$adviceText") + } + } + } + } else { + runAttest.setIcon(R.drawable.ic_circle_error) + runAttest.summary = context.getString(R.string.pref_test_summary_failed, status?.statusMessage) + } + } + } + }, Random.nextBytes(32), "AIzaSyAfcNLBpWkqrt50mluU6GswUmtysmLn9cY") + true + } + runReCaptcha.setOnPreferenceClickListener { + val context = context ?: return@setOnPreferenceClickListener false + runReCaptcha.setIcon(R.drawable.ic_circle_pending) + runReCaptcha.setSummary(R.string.pref_test_summary_running) + val handler = Handler(Looper.myLooper()!!) + SafetyNetClientServiceImpl(context, "com.blogspot.android_er.recaptcha", lifecycle).verifyWithRecaptcha(object : ISafetyNetCallbacks.Default() { + override fun onRecaptchaResult(status: Status?, recaptchaResultData: RecaptchaResultData?) { + handler.post { + if (status?.isSuccess == true) { + runReCaptcha.setIcon(R.drawable.ic_circle_check) + runReCaptcha.setSummary(R.string.pref_test_summary_passed) + } else { + runReCaptcha.setIcon(R.drawable.ic_circle_error) + runReCaptcha.summary = context.getString(R.string.pref_test_summary_failed, status?.statusMessage) + } + } + } + }, "6LdMKyUUAAAAAN0ndw7byI03_qpbpjxKY-mTQnLw") + true + } + } } diff --git a/play-services-core/src/main/res/drawable/ic_circle_check.xml b/play-services-core/src/main/res/drawable/ic_circle_check.xml new file mode 100644 index 00000000..748f055a --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_circle_check.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-core/src/main/res/drawable/ic_circle_error.xml b/play-services-core/src/main/res/drawable/ic_circle_error.xml new file mode 100644 index 00000000..30e18f04 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_circle_error.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/play-services-core/src/main/res/drawable/ic_circle_pending.xml b/play-services-core/src/main/res/drawable/ic_circle_pending.xml new file mode 100644 index 00000000..9d2c37e7 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_circle_pending.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/play-services-core/src/main/res/drawable/ic_circle_warn.xml b/play-services-core/src/main/res/drawable/ic_circle_warn.xml new file mode 100644 index 00000000..842fc461 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_circle_warn.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-core/src/main/res/layout/safety_net_advanced_fragment.xml b/play-services-core/src/main/res/layout/safety_net_advanced_fragment.xml new file mode 100644 index 00000000..2e72a670 --- /dev/null +++ b/play-services-core/src/main/res/layout/safety_net_advanced_fragment.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/play-services-core/src/main/res/values-be/strings.xml b/play-services-core/src/main/res/values-be/strings.xml index 8cd7f17c..fa3f09f4 100644 --- a/play-services-core/src/main/res/values-be/strings.xml +++ b/play-services-core/src/main/res/values-be/strings.xml @@ -114,7 +114,6 @@ Канфігурацыя Google сэрвісы Служба вызначэння месцазнаходжання - Рэжым работы Сэрвісы Тэст @@ -181,21 +180,10 @@ Незарэгістраваныя прыкладанні Сеткі для прыёму push-паведамленняў - Google SafetyNet гэта сістэма сертыфікацыі прылады, якая гарантуе, што прылада карэктна абаронена і сумяшчальна з Android CTS. Пэўныя праграмы выкарыстоўваюць SafetyNet з меркаванняў бяспекі або ў якасці папярэдняй сістэмы абароны ад узлому.\n\nmicroG GmsCore змяшчае свабодную рэалізацыю SafetyNet, але афіцыйны сервер патрабуе, каб запыты былі падпісаны з дапамогай прапрыетарнай сістэмы DroidGuard. Ізаляваная версія DroidGuard даступная як асобнае прыкладанне "DroidGuard Helper". + Google SafetyNet гэта сістэма сертыфікацыі прылады, якая гарантуе, што прылада карэктна абаронена і сумяшчальна з Android CTS. Пэўныя праграмы выкарыстоўваюць SafetyNet з меркаванняў бяспекі або ў якасці папярэдняй сістэмы абароны ад узлому.\n\nmicroG GmsCore змяшчае свабодную рэалізацыю SafetyNet, але афіцыйны сервер патрабуе, каб запыты былі падпісаны з дапамогай прапрыетарнай сістэмы DroidGuard. Дазволіць праверку статусу прылады - Тэст верыфікацыі SafetyNet - - Выкарыстоўваць афіцыйны сервер - Патрабуецца сістэма без root і ўсталяваны microG DroidGuard Helper - афіцыйны сервер - Выкарыстоўваць іншы сервер - Іншыя серверы могуць быць у стане адказаць на запыты SafetyNet без подпісу DroidGuard - іншы сервер - URL іншага сервера - Поўны URL іншага сервера, які адказвае на праверачныя запыты SafetyNet - Выкарыстоўваць самастойна падпісаны сертыфікат - Замест запытаў на сервер падпісваць SafetyNet лакальна, выкарыстоўваючы самастойна створаны сертыфікат. Большасць прыкладанняў будуць адмаўляцца выкарыстоўваць самастойна падпісаныя адказы. - самастойна падпісаны сертыфікат + Тэст верыфікацыі SafetyNet + Рэжым работы 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 08e3579f..028a0acb 100644 --- a/play-services-core/src/main/res/values-de/strings.xml +++ b/play-services-core/src/main/res/values-de/strings.xml @@ -114,7 +114,6 @@ Dies kann einige Minuten dauern." Einstellungen Google-Dienste Standortdienst - Modus Dienste Test @@ -180,21 +179,10 @@ Dies kann einige Minuten dauern." Nicht-registrierte Apps Verwendbare Netzwerke - Google SafetyNet ist ein System, um Geräte zu zertifizieren und so sicherzustellen, dass sie ausreichend geschützt und kompatibel mit Android sind. Einige Anwendungen benutzen SafetyNet aus Sicherheitsgründen oder um einen Kopierschutz zu erzwingen.\n\nmicroG GmsCore enthält eine freie Implementierung von SafetyNet, jedoch verlangen die Google-Server, dass die Anfragen durch das proprietäre DroidGuard signiert sind. Eine unschädliche gemachte Version von DroidGuard ist als separate \"DroidGuard Helper\"-App verfügbar. + Google SafetyNet ist ein System, um Geräte zu zertifizieren und so sicherzustellen, dass sie ausreichend geschützt und kompatibel mit Android sind. Einige Anwendungen benutzen SafetyNet aus Sicherheitsgründen oder um einen Kopierschutz zu erzwingen.\n\nmicroG GmsCore enthält eine freie Implementierung von SafetyNet, jedoch verlangen die Google-Server, dass die Anfragen durch das proprietäre DroidGuard signiert sind. Geräte-Zertifizierung erlauben - SafetyNet-Zertifizierung testen - - Offizielle Server nutzen - Erfordert eine ungerootetes ROM und den microG DroidGuard Helper - offizieller Server - Alternativen Server nutzen - Alternative Server können auch SafetyNet-Anfragen beantworten, die nicht durch DroidGuard signiert wurden - Dritt-Server - Alternative Server URL - Vollständige URL des alternativen Servers, der SafetyNet-Anfragen beantwortet - Selbst signieren - Statt einen Server zu nutzen, die SafetyNet Signatur lokal mit einem eigens erstellten Zertifikat signieren. Die meisten Apps werden diese Signaturen nicht akzeptieren. - Selbst-signiertes Zertifikat + SafetyNet-Zertifizierung testen + Modus 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 28e9a118..f3ebc241 100644 --- a/play-services-core/src/main/res/values-es/strings.xml +++ b/play-services-core/src/main/res/values-es/strings.xml @@ -112,7 +112,6 @@ Esto podría tardar algunos minutos." Configuración Servicios de Google Servicio de localización - Modo de operación Servicios Prueba @@ -179,21 +178,10 @@ Esto podría tardar algunos minutos." Aplicaciones no registradas Redes a utilizar para las notificaciones push - Google SafetyNet es un sistema de certificación de dispositivos, que garantiza que el dispositivo está correctamente asegurado y es compatible con Android CTS. Algunas aplicaciones utilizan SafetyNet por razones de seguridad o como un prerrequisito para la protección contra manipulaciones.\n\nmicroG GmsCore contiene una implementación gratuita de SafetyNet, pero el servidor oficial requiere que las solicitudes de SafetyNet sean firmadas utilizando el sistema propietario DroidGuard. Una versión en sandbox de DroidGuard está disponible como una aplicación separada "DroidGuard Helper". + Google SafetyNet es un sistema de certificación de dispositivos, que garantiza que el dispositivo está correctamente asegurado y es compatible con Android CTS. Algunas aplicaciones utilizan SafetyNet por razones de seguridad o como un prerrequisito para la protección contra manipulaciones.\n\nmicroG GmsCore contiene una implementación gratuita de SafetyNet, pero el servidor oficial requiere que las solicitudes de SafetyNet sean firmadas utilizando el sistema propietario DroidGuard. Permitir la certificación del dispositivo - Probar el certificado de SafetyNet - - Usar el servidor oficial - Requiere un sistema no root y un microG DroidGuard Helper instalado - servidor oficial - Usar un servidor de terceros - Los servidores de terceros podrían responder a las solicitudes de SafetyNet sin la firma de DroidGuard - servidor de terceros - URL del servidor personalizada - URL completa del servidor de terceros que responde a las solicitudes de certificación de SafetyNet - Usar un certificado autofirmado - En lugar de solicitar un servidor, firma las respuestas de SafetyNet localmente usando un certificado autofirmado. La mayoría de las aplicaciones se negarán a usar respuestas autofirmadas. - certificado autofirmado + Probar el certificado de SafetyNet + Modo de operación 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 02d823dd..e0b44f25 100644 --- a/play-services-core/src/main/res/values-fr/strings.xml +++ b/play-services-core/src/main/res/values-fr/strings.xml @@ -102,7 +102,6 @@ Ceci peut prendre plusieurs minutes." Configuration Services Google Service de localisation - Mode d’opération Services Test @@ -149,18 +148,8 @@ Ceci peut prendre plusieurs minutes." 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. Une version mise en « bac-à-sable » de DroidGuard est disponible dans une application séparée « DroidGuard Helper ». - - - Tester la certification SafetyNet - - Utiliser les serveurs officiels - Nécessite un système non-rooté et microG DroidGuard Helper installé - Utiliser un serveur tiers - Les serveurs tiers peuvent être capable de répondre aux requêtes SafetyNet sans signature de DroidGuard. - URL serveur tiers - URL complète du serveur tiers répondant aux requêtes de certification SafetyNet - Utiliser un certificat auto-signé - Au lieu de requérir un serveur, signer les réponses SafetyNet localement en utilisant un certificat auto-signé. La plupart des applications refuseront d’utiliser des réponses auto-signées. + 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. + Tester la certification SafetyNet + Mode d’opération 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 30ed69ad..dc17c400 100644 --- a/play-services-core/src/main/res/values-it/strings.xml +++ b/play-services-core/src/main/res/values-it/strings.xml @@ -106,7 +106,6 @@ Questa operazione può richiedere alcuni secondi." Configurazione Servizi Google Servizi di localizzazione - Modalità operativa Servizi Sperimentale @@ -174,21 +173,10 @@ Questa operazione può richiedere alcuni secondi." Applicazioni non registrate Reti da utilizzare per le notifiche push - SafetyNet di Google è un sistema di certificazione del dispositivo che ne garantisce la sicurezza e la compatibilità con Android CTS. Alcune applicazioni utilizzano SafetyNet per ragioni di sicurezza o come prerequisito per la protezione da manomissione.\n\nUn\'implementazione libera di SafetyNet è contenuta in microG, tuttavia i server ufficiali richiedono che le richieste SafetyNet siano firmate utilizzando il sistema proprietario DroidGuard. Una versione isolata di DroidGuard è disponibile all\'interno dell\'applicazione “microG DroidGuard Helper”. + SafetyNet di Google è un sistema di certificazione del dispositivo che ne garantisce la sicurezza e la compatibilità con Android CTS. Alcune applicazioni utilizzano SafetyNet per ragioni di sicurezza o come prerequisito per la protezione da manomissione.\n\nUn\'implementazione libera di SafetyNet è contenuta in microG, tuttavia i server ufficiali richiedono che le richieste SafetyNet siano firmate utilizzando il sistema proprietario DroidGuard. Permetti l\'attestazione del dispositivo - Prova l\'attestazione di SafetyNet - - Utilizza i server ufficiali - Richiede un sistema senza privilegi di root e con l\'applicazione "microG DroidGuard Helper" installata - Server ufficiale - Utilizza un server di terze parti - I server di terze parti potrebbero essere in grado di rispondere alle richieste di SafetyNet senza la firma di DroidGuard - Server di terze parti - URL del server personalizzato - URL completo del server personalizzato che risponde alle richieste di attestazione SafetyNet - Utilizza un certificato auto-firmato - Anziché inoltrare le richieste a un server, firma localmente le risposte SafetyNet utilizzando un certificato auto-firmato. La maggior parte delle applicazioni rifiuteranno l\'uso di risposte auto-firmate. - Certificato auto-firmato + Prova l\'attestazione di SafetyNet + Modalità operativa diff --git a/play-services-core/src/main/res/values-ja/strings.xml b/play-services-core/src/main/res/values-ja/strings.xml index 2cc5b3f2..d37a1429 100644 --- a/play-services-core/src/main/res/values-ja/strings.xml +++ b/play-services-core/src/main/res/values-ja/strings.xml @@ -109,7 +109,6 @@ 設定 Googleサービス 位置情報サービス - 動作モード サービス テスト @@ -171,20 +170,9 @@ Google SafetyNetはデバイス認証システムであり、デバイスが適切に保護され、Android CTSと互換性があることを保証します。 一部のアプリケーションは、セキュリティ上の理由または改ざん防止の前提条件としてSafetyNetを使用します。 - microG GmsCoreにはSafetyNetのオープンソースな実装が含まれていますが、公式サーバーでは、プロプライエタリなDroidGuardシステムを使用してSafetyNetリクエストに署名する必要があります。 DroidGuardのサンドボックスバージョンは、個別の「DroidGuardHelper」アプリとして利用できます。 + microG GmsCoreにはSafetyNetのオープンソースな実装が含まれていますが、公式サーバーでは、プロプライエタリなDroidGuardシステムを使用してSafetyNetリクエストに署名する必要があります。 デバイスの認証を許可 - SafetyNetテストの実行 - - 公式サーバーを使用 - root化されていないシステムと、microG DroidGuardHelperのインストールが必要です。 - 公式サーバー - サードパーティのサーバーを使用 - サードパーティのサーバーは、DroidGuardの署名がないSafetyNetリクエストに応答できる場合があります - third-party server - カスタムサーバーのURL - SafetyNet認証リクエストに応答するサードパーティサーバーの完全なURL - 自己署名証明書を使用 - サーバーにリクエストする代わりに、自己署名証明書を使用してローカルでSafetyNet応答に署名します。 ほとんどのアプリは、自己署名証明書を使用した応答の使用を拒否します。 - 自己署名証明書 + SafetyNetテストの実行 + 動作モード 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 16b8a046..bc2206b2 100644 --- a/play-services-core/src/main/res/values-pl/strings.xml +++ b/play-services-core/src/main/res/values-pl/strings.xml @@ -102,7 +102,6 @@ To zajmie kilka minut. Konfiguracja Usługi Google Usługa lokalizacji - Tryb działania Usługi w tle Test @@ -147,17 +146,8 @@ To zajmie kilka minut. Odmówiłeś już zarejestrowanej aplikacji zarejestrować się w usłudze powiadomień ‘push’.\nCzy chcesz ją wyrejestrować, aby nie otrzymywała powiadomień ‘push’ w przyszłości? Wiadomości: %1$d (%2$d bajtów) - Google SafetyNet jest systemem certyfikacji urządzenia, który upewnia się czy urządzenie jest poprawnie zabezpieczone i kompatybilne z Android CTS. Niektóre aplikacje używają SafetyNet ze względów bezpieczeństwa lub jako przeciwśrodek do modyfikacji.\n\nUsługa microG GmsCore zawiera wolną implementację SafetyNet, ale oficjalny serwer wymaga by SafetyNet był podpisany przez własnościowy system DroidGuard. Specjalna wersja DroidGuard-a jest dostępna do pobrania jako oddzielna aplikacja “DroidGuard Helper” w repozytorium miroG w F-Droid. - - Wypróbuj działanie SafetyNet - - Użyj oficjalnego serwera - Wymaga niezrootowanego ROM-u i zainstalowanego microG DroidGuard Helper - Użyj serwera strony trzeciej - Serwery stron trzecich mogą być w stanie odpowiedzieć na zapytania SafetyNet bez sygnatury DroidGuard - URL własnego serwera - Pełny adres URL serwera strony trzeciej odpowiadającego na zapytania SafetyNet - Użyj samo-podpisanego certyfikatu - Zamiast sprawdzać serwer, podpisuj odpowiedzi SafetyNet lokalnie używając samo-podpisanego certyfikatu. Większość aplikacji odmówi użycia samo-podpisanych odpowiedzi. + Google SafetyNet jest systemem certyfikacji urządzenia, który upewnia się czy urządzenie jest poprawnie zabezpieczone i kompatybilne z Android CTS. Niektóre aplikacje używają SafetyNet ze względów bezpieczeństwa lub jako przeciwśrodek do modyfikacji.\n\nUsługa microG GmsCore zawiera wolną implementację SafetyNet, ale oficjalny serwer wymaga by SafetyNet był podpisany przez własnościowy system DroidGuard. + Wypróbuj działanie SafetyNet + Tryb działania diff --git a/play-services-core/src/main/res/values-ru/strings.xml b/play-services-core/src/main/res/values-ru/strings.xml index 2eec92ba..172223c7 100644 --- a/play-services-core/src/main/res/values-ru/strings.xml +++ b/play-services-core/src/main/res/values-ru/strings.xml @@ -114,7 +114,6 @@ Конфигурация Google сервисы Служба определения местоположения - Режим работы Сервисы Тест @@ -181,22 +180,11 @@ Незарегистрированные приложения Сети для приёма push-уведомлений - Google SafetyNet это система сертификации устройства, гарантирующая, что устройство корректно защищено и совместимо с Android CTS. Некоторые приложения используют SafetyNet из соображений безопасности или в качестве предварительной системы защиты от взлома.\n\nmicroG GmsCore содержит свободную реализацию SafetyNet, но официальный сервер требует, чтобы запросы были подписаны с помощью проприетарной системы DroidGuard. Изолированная версия DroidGuard доступна как отдельное приложение "DroidGuard Helper". + Google SafetyNet это система сертификации устройства, гарантирующая, что устройство корректно защищено и совместимо с Android CTS. Некоторые приложения используют SafetyNet из соображений безопасности или в качестве предварительной системы защиты от взлома.\n\nmicroG GmsCore содержит свободную реализацию SafetyNet, но официальный сервер требует, чтобы запросы были подписаны с помощью проприетарной системы DroidGuard. Разрешить проверку статуса устройства - Тест верификации SafetyNet - - Использовать официальный сервер - Требуется система без root и установленный microG DroidGuard Helper - официальный сервер - Использовать сторонний сервер - Сторонние сервера могут быть в состоянии ответить на запросы SafetyNet без подписи DroidGuard - сторонний сервер - URL стороннего сервера - Полный URL стороннего сервера, который отвечает на проверочные запросы SafetyNet - Использовать самоподписанный сертификат - Вместо запросов на сервер подписывать SafetyNet локально, используя самостоятельно созданный сертификат. Большинство приложений будут отказываться использовать самоподписанные ответы. - самоподписанный сертификат + Тест верификации SafetyNet + Режим работы 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 ace13272..f9246c9c 100644 --- a/play-services-core/src/main/res/values-uk/strings.xml +++ b/play-services-core/src/main/res/values-uk/strings.xml @@ -109,7 +109,6 @@ Конфігурація Сервіси Google Сервіси позиціювання - Режим роботи Сервіси Тест @@ -154,17 +153,8 @@ Ви заборонили додатку push-повідомлення, який вже був прив\'язаний.\nВи бажаєте відв\'язати його зараз, аби більше не отримувати від нього push-повідомлень? Повідомлень: %1$d (%2$d байт) - Google SafetyNet це система сертифікації пристрою, яка гарантує, що пристрій коректно захищено та сумісне із Android CTS. Деякі додатки використовують SafetyNet для безпеки або в якості попередньої системи захисту від злому.\n\nmicroG GmsCore містить вільну реалізацію SafetyNet, але офіційний сервер вимагає, аби запити були підписані за допомогою закритої програмної системи DroidGuard. Ізольована версія DroidGuard доступна для встановлення як окремий додаток \"DroidGuard Helper\". - - Протестувати SafetyNet перевірку - - Використовувати офіційний сервер - Потребує систему з адміністративними правами та встановленим microG DroidGuard Helper - Використовувати сторонній сервер - Сторонні сервери можуть відповідати на SafetyNet запити без підпису DroidGuard - Посилання стороннього серверу - Повне посилання стороннього серверу, який відповідає на запити SafetyNet - Використовувати самостійно підписаний сертифікат - Підписувати SafetyNet локально, замість запитів на сервер, використовуючи само-підписний сертифікат. Більшість додатків будуть відхиляти само-підписні відповіді. + Google SafetyNet це система сертифікації пристрою, яка гарантує, що пристрій коректно захищено та сумісне із Android CTS. Деякі додатки використовують SafetyNet для безпеки або в якості попередньої системи захисту від злому.\n\nmicroG GmsCore містить вільну реалізацію SafetyNet, але офіційний сервер вимагає, аби запити були підписані за допомогою закритої програмної системи DroidGuard. + Протестувати SafetyNet перевірку + Режим роботи diff --git a/play-services-core/src/main/res/values-zh-rTW/strings.xml b/play-services-core/src/main/res/values-zh-rTW/strings.xml index 1a9c71cd..aad94624 100644 --- a/play-services-core/src/main/res/values-zh-rTW/strings.xml +++ b/play-services-core/src/main/res/values-zh-rTW/strings.xml @@ -138,16 +138,7 @@ Google SafetyNet是一個確認手機被確實保護和相容於Android CTS的驗證系統。一些程式基於安全因素使用SafetyNet,一些則是以必須通過的形式來防止篡改。 - microG GmsCore包含了SafetyNet的自由執行,但官方伺服器需要SafetyNet請求經非自由軟體性質的DroidGuard系統簽名。有一個可用的沙盒測試版本DroidGuard:獨立程式“DroidGuard Helper”。 + microG GmsCore包含了SafetyNet的自由執行,但官方伺服器需要SafetyNet請求經非自由軟體性質的DroidGuard系統簽名。 - 測試SafetyNet驗證 - - 使用官方伺服器 - 需要未root的系統及安裝microG DroidGuard Helper - 使用第三方伺服器 - 第三方伺服器可能不需要DroidGuard簽名便能回應SafetyNet請求 - 自訂伺服器網址 - 回應SafetyNet驗證請求之第三方伺服器的完整網址 - 使用自行簽名認證 - 不向伺服器請求簽名,而是用本地的認證文件自行簽名SafetyNet。大部分的程式都會拒絕自行簽名認證。 + 測試SafetyNet驗證 diff --git a/play-services-core/src/main/res/values/strings.xml b/play-services-core/src/main/res/values/strings.xml index 2f2e5752..499d43fc 100644 --- a/play-services-core/src/main/res/values/strings.xml +++ b/play-services-core/src/main/res/values/strings.xml @@ -1,19 +1,8 @@ - - microG Services Core microG Settings @@ -114,7 +103,6 @@ This can take a couple of minutes." Configuration Google Services Location service - Operation mode Services Test @@ -182,21 +170,15 @@ This can take a couple of minutes." Unregistered apps Networks to use for push notifications - Google SafetyNet is a device certification system, ensuring that the device is properly secured and compatible with Android CTS. Some applications use SafetyNet for security reasons or as a prerequisite for tamper-protection.\n\nmicroG GmsCore contains a free implementation of SafetyNet, but the official server requires SafetyNet requests to be signed using the proprietary DroidGuard system. A sandboxed version of DroidGuard is available as a separate “DroidGuard Helper” app. + Google SafetyNet is a device certification system, ensuring that the device is properly secured and compatible with Android CTS. Some applications use SafetyNet for security reasons or as a prerequisite for tamper-protection.\n\nmicroG GmsCore contains a free implementation of SafetyNet, but the official server requires SafetyNet requests to be signed using the proprietary DroidGuard system. Allow device attestation - Try SafetyNet attestation - - Use official server - Requires an unrooted system and microG DroidGuard Helper installed - official server - Use third-party server - Third-party servers might be able to reply to SafetyNet requests without DroidGuard signature - third-party server - Custom server URL - Full URL of the third-party server answering SafetyNet attestation requests - Use self-signed certificate - Instead of requesting a server, sign SafetyNet responses locally using a self-signed certificate. Most apps will refuse to use self-signed responses. - self-signed certificate + Test SafetyNet attestation + Test ReCAPTCHA + Passed all tests + Failed: %s + Warning: %s + Running… + Operation mode diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index 1f73af33..6c82e63c 100644 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -6,17 +6,29 @@ + + + + diff --git a/play-services-core/src/main/res/xml/preferences_gcm.xml b/play-services-core/src/main/res/xml/preferences_gcm.xml deleted file mode 100644 index bcfc63bb..00000000 --- a/play-services-core/src/main/res/xml/preferences_gcm.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/play-services-core/src/main/res/xml/preferences_push_notifications.xml b/play-services-core/src/main/res/xml/preferences_push_notifications.xml index 0fd4e926..871ad6cf 100644 --- a/play-services-core/src/main/res/xml/preferences_push_notifications.xml +++ b/play-services-core/src/main/res/xml/preferences_push_notifications.xml @@ -26,7 +26,7 @@ android:layout="@layout/preference_category_no_label"> diff --git a/play-services-core/src/main/res/xml/preferences_safetynet.xml b/play-services-core/src/main/res/xml/preferences_safetynet.xml index 2c4dfbbf..438395af 100644 --- a/play-services-core/src/main/res/xml/preferences_safetynet.xml +++ b/play-services-core/src/main/res/xml/preferences_safetynet.xml @@ -17,19 +17,26 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + android:layout="@layout/preference_category_no_label"> + android:title="@string/pref_safetynet_test_title" + tools:icon="@drawable/ic_circle_check" + tools:summary="@string/pref_test_summary_passed" /> + + + + diff --git a/play-services-core/src/main/res/xml/preferences_snet_advanced.xml b/play-services-core/src/main/res/xml/preferences_snet_advanced.xml index e87859af..e50f5ba9 100644 --- a/play-services-core/src/main/res/xml/preferences_snet_advanced.xml +++ b/play-services-core/src/main/res/xml/preferences_snet_advanced.xml @@ -15,29 +15,12 @@ ~ limitations under the License. --> - - - - - - + + + - \ No newline at end of file + diff --git a/play-services-core/src/withNearby/kotlin/org/microg/gms/ui/NearbyPreferencesIntegration.kt b/play-services-core/src/withNearby/kotlin/org/microg/gms/ui/NearbyPreferencesIntegration.kt index 17d20c37..d4f940e1 100644 --- a/play-services-core/src/withNearby/kotlin/org/microg/gms/ui/NearbyPreferencesIntegration.kt +++ b/play-services-core/src/withNearby/kotlin/org/microg/gms/ui/NearbyPreferencesIntegration.kt @@ -12,11 +12,12 @@ import android.net.Uri import androidx.core.content.ContextCompat import com.google.android.gms.R import org.microg.gms.nearby.exposurenotification.Constants +import org.microg.gms.nearby.exposurenotification.ExposurePreferences import org.microg.gms.nearby.exposurenotification.getExposureNotificationsServiceInfo interface NearbyPreferencesIntegration { companion object { - suspend fun getExposurePreferenceSummary(context: Context): String = if (isAvailable && getExposureNotificationsServiceInfo(context).configuration.enabled) { + suspend fun getExposurePreferenceSummary(context: Context): String = if (isAvailable && ExposurePreferences(context).enabled) { context.getString(R.string.service_status_enabled_short) } else { context.getString(R.string.service_status_disabled_short) diff --git a/play-services-droidguard-core-ui/build.gradle b/play-services-droidguard-core-ui/build.gradle new file mode 100644 index 00000000..90ad5e30 --- /dev/null +++ b/play-services-droidguard-core-ui/build.gradle @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + implementation project(':play-services-droidguard-core') + implementation project(':play-services-base-core-ui') + + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation "androidx.preference:preference-ktx:$preferenceVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main { + java.srcDirs = ['src/main/kotlin'] + } + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'UI for microG service implementation for play-services-droidguard' diff --git a/play-services-droidguard-core-ui/src/main/AndroidManifest.xml b/play-services-droidguard-core-ui/src/main/AndroidManifest.xml new file mode 100644 index 00000000..400eff15 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/ContainedEditTextPreference.kt b/play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/ContainedEditTextPreference.kt new file mode 100644 index 00000000..5fcde75b --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/ContainedEditTextPreference.kt @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard.core.ui + +import android.content.Context +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import androidx.core.widget.addTextChangedListener +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder + +class ContainedEditTextPreference : Preference { + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context) : super(context) + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val editText = holder.itemView.findViewById(android.R.id.edit) + (editText as? TextWatcher)?.let { editText.removeTextChangedListener(it) } + editText.addTextChangedListener { textChangedListener(it?.toString() ?: "") } + editText.tag = this + editText.hint = hint + editText.text.replace(0, editText.text.length, text) + editText.isEnabled = editable + if (requestFocus) { + editText.requestFocus() + (editText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) + requestFocus = false + } + } + + private var requestFocus: Boolean = false + fun editRequestFocus() { + requestFocus = true + notifyChanged() + } + + var textChangedListener: (String) -> Unit = {} + + var editable: Boolean = true + set(value) { + field = value + notifyChanged() + } + + var text: String = "" + set(value) { + field = value + notifyChanged() + } + + var hint: String = "" + set(value) { + field = value + notifyChanged() + } + + init { + layoutResource = R.layout.preference_material_with_widget_below + widgetLayoutResource = R.layout.preference_edit_widget + } +} diff --git a/play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/DroidGuardPreferencesFragment.kt b/play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/DroidGuardPreferencesFragment.kt new file mode 100644 index 00000000..db3eed15 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/kotlin/org/microg/gms/droidguard/core/ui/DroidGuardPreferencesFragment.kt @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.droidguard.core.ui + +import android.os.Bundle +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import org.microg.gms.droidguard.core.DroidGuardPreferences +import org.microg.gms.droidguard.core.DroidGuardPreferences.Mode.Embedded +import org.microg.gms.droidguard.core.DroidGuardPreferences.Mode.Network +import org.microg.gms.droidguard.core.ui.R.drawable.ic_radio_checked +import org.microg.gms.droidguard.core.ui.R.drawable.ic_radio_unchecked + +class DroidGuardPreferencesFragment : PreferenceFragmentCompat() { + private lateinit var modeEmbedded: Preference + private lateinit var modeNetwork: ContainedEditTextPreference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_droidguard) + } + + override fun onBindPreferences() { + modeEmbedded = preferenceScreen.findPreference("pref_droidguard_mode_embedded") ?: modeEmbedded + modeNetwork = preferenceScreen.findPreference("pref_droidguard_mode_network") ?: modeNetwork + modeEmbedded.setOnPreferenceClickListener { + DroidGuardPreferences.setMode(it.context, Embedded) + updateConfiguration() + true + } + modeNetwork.setOnPreferenceClickListener { + DroidGuardPreferences.setMode(it.context, Network) + modeNetwork.editRequestFocus() + updateConfiguration() + true + } + modeNetwork.textChangedListener = { + DroidGuardPreferences.setNetworkServerUrl(requireContext(), it) + } + updateConfiguration() + } + + fun updateConfiguration() { + val mode = DroidGuardPreferences.getMode(requireContext()) + modeEmbedded.setIcon(if (mode == Embedded) ic_radio_checked else ic_radio_unchecked) + modeNetwork.setIcon(if (mode == Network) ic_radio_checked else ic_radio_unchecked) + modeNetwork.text = DroidGuardPreferences.getNetworkServerUrl(requireContext()) ?: "" + modeNetwork.editable = mode == Network + modeNetwork.hint = "https://example.com/droidguard/" + } +} diff --git a/play-services-droidguard-core-ui/src/main/res/layout/preference_edit_widget.xml b/play-services-droidguard-core-ui/src/main/res/layout/preference_edit_widget.xml new file mode 100644 index 00000000..193a6cd7 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/res/layout/preference_edit_widget.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/play-services-droidguard-core-ui/src/main/res/layout/preference_material_with_widget_below.xml b/play-services-droidguard-core-ui/src/main/res/layout/preference_material_with_widget_below.xml new file mode 100644 index 00000000..72c619e9 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/res/layout/preference_material_with_widget_below.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + diff --git a/play-services-droidguard-core-ui/src/main/res/values/strings.xml b/play-services-droidguard-core-ui/src/main/res/values/strings.xml new file mode 100644 index 00000000..a7a66403 --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ + + + + DroidGuard operation mode + Embedded + Use local DroidGuard runtime + Remote + Connect to DroidGuard runtime via network + diff --git a/play-services-droidguard-core-ui/src/main/res/xml/preferences_droidguard.xml b/play-services-droidguard-core-ui/src/main/res/xml/preferences_droidguard.xml new file mode 100644 index 00000000..995f0a1d --- /dev/null +++ b/play-services-droidguard-core-ui/src/main/res/xml/preferences_droidguard.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/settings.gradle b/settings.gradle index 601c7f71..f8812b76 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ include ':play-services-droidguard-api' include ':play-services-iid-api' include ':play-services-location-api' include ':play-services-nearby-api' +include ':play-services-safetynet-api' include ':play-services-tapandpay-api' include ':play-services-vision-api' include ':play-services-vision-common-api' @@ -38,6 +39,7 @@ include ':firebase-dynamic-links-api' include ':play-services-core-proto' include ':play-services-droidguard-core-proto' include ':play-services-nearby-core-proto' +include ':play-services-safetynet-core-proto' include ':play-services-wearable-proto' include ':play-services-basement-ktx' @@ -53,10 +55,12 @@ include ':play-services-maps-core-mapbox' include ':play-services-maps-core-vtm' include ':play-services-maps-core-vtm:vtm-microg-theme' include ':play-services-nearby-core' +include ':play-services-safetynet-core' include ':play-services-tapandpay-core' include ':play-services-vision-core' include ':play-services-base-core-ui' +include ':play-services-droidguard-core-ui' include ':play-services-nearby-core-ui' include ':firebase-auth-core' From 9304375da769da2ecc370ed4abe9fee92f05f53b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:51:21 +0100 Subject: [PATCH 42/63] ENF: Use new base features --- .../exposurenotification/ExposureNotificationServiceImpl.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt index 4ea375ee..6e963ae9 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt @@ -277,9 +277,9 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } ExposureSummary.ExposureSummaryBuilder() - .setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.min()?.toInt() ?: 0) + .setDaysSinceLastExposure(exposures.map { it.daysSinceExposure }.minOrNull()?.toInt() ?: 0) .setMatchedKeyCount(exposures.map { it.key }.distinct().size) - .setMaximumRiskScore(exposures.map { it.getRiskScore(configuration) }.max()?.toInt() ?: 0) + .setMaximumRiskScore(exposures.map { it.getRiskScore(configuration) }.maxOrNull()?.toInt() ?: 0) .setAttenuationDurations(intArrayOf( exposures.map { it.getAttenuationDurations(configuration)[0] }.sum(), exposures.map { it.getAttenuationDurations(configuration)[1] }.sum(), @@ -784,7 +784,7 @@ class ExposureNotificationServiceImpl(private val context: Context, private val } } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(TAG, code, reply, flags) { super.onTransact(code, data, reply, flags) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } companion object { private val tempGrantedPermissions: MutableSet> = hashSetOf() From 854f879da4a056bd9b0b8a365d676147dc7c73ea Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:52:31 +0100 Subject: [PATCH 43/63] Update checkin service API --- .../android/gms/checkin/internal/ICheckinService.aidl | 2 ++ .../java/org/microg/gms/checkin/CheckinService.java | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/play-services-api/src/main/aidl/com/google/android/gms/checkin/internal/ICheckinService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/checkin/internal/ICheckinService.aidl index 6ac61289..973de92f 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/checkin/internal/ICheckinService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/checkin/internal/ICheckinService.aidl @@ -2,4 +2,6 @@ package com.google.android.gms.checkin.internal; interface ICheckinService { String getDeviceDataVersionInfo(); + long getLastCheckinSuccessTime(); + String getLastSimOperator(); } diff --git a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java index 9db1ca5d..deb520fe 100644 --- a/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java +++ b/play-services-core/src/main/java/org/microg/gms/checkin/CheckinService.java @@ -59,6 +59,16 @@ public class CheckinService extends IntentService { public String getDeviceDataVersionInfo() throws RemoteException { return LastCheckinInfo.read(CheckinService.this).getDeviceDataVersionInfo(); } + + @Override + public long getLastCheckinSuccessTime() throws RemoteException { + return LastCheckinInfo.read(CheckinService.this).getLastCheckin(); + } + + @Override + public String getLastSimOperator() throws RemoteException { + return null; + } }; public CheckinService() { From 10de88b89f7f0c60834be300cf3f09bb6df7cdac Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:54:05 +0100 Subject: [PATCH 44/63] Add new Auth API features --- .../gms/auth/api/internal/IAuthCallbacks.aidl | 8 + .../gms/auth/api/internal/IAuthService.aidl | 11 ++ .../gms/auth/api/proxy/ProxyRequest.aidl | 3 + .../gms/auth/api/proxy/ProxyResponse.aidl | 3 + .../gms/auth/appcert/IAppCertService.aidl | 6 + .../gms/auth/api/proxy/ProxyRequest.java | 41 +++++ .../gms/auth/api/proxy/ProxyResponse.java | 30 ++++ .../src/main/proto/appcert.proto | 33 ++++ .../microg/gms/auth/appcert/AppCertManager.kt | 170 ++++++++++++++++++ .../microg/gms/auth/appcert/AppCertService.kt | 42 +++++ .../CredentialPickerActivity.kt | 4 +- .../{ => credentials}/CredentialsService.kt | 9 +- .../microg/gms/auth/proxy/AuthProxyService.kt | 63 +++++++ 13 files changed, 418 insertions(+), 5 deletions(-) create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthCallbacks.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthService.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyRequest.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyResponse.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/auth/appcert/IAppCertService.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java create mode 100644 play-services-core-proto/src/main/proto/appcert.proto create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt rename play-services-core/src/main/kotlin/org/microg/gms/auth/{ => credentials}/CredentialPickerActivity.kt (94%) rename play-services-core/src/main/kotlin/org/microg/gms/auth/{ => credentials}/CredentialsService.kt (81%) create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthCallbacks.aidl new file mode 100644 index 00000000..ae8e79c5 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthCallbacks.aidl @@ -0,0 +1,8 @@ +package com.google.android.gms.auth.api.internal; + +import com.google.android.gms.auth.api.proxy.ProxyResponse; + +interface IAuthCallbacks { + void onProxyResponse(in ProxyResponse response) = 0; + void onSpatulaHeader(String spatulaHeader) = 1; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthService.aidl new file mode 100644 index 00000000..a5f6a537 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/internal/IAuthService.aidl @@ -0,0 +1,11 @@ +package com.google.android.gms.auth.api.internal; + +import com.google.android.gms.auth.api.internal.IAuthCallbacks; +//import com.google.android.gms.auth.api.proxy.ProxyGrpcRequest; +import com.google.android.gms.auth.api.proxy.ProxyRequest; + +interface IAuthService { + void performProxyRequest(IAuthCallbacks callbacks, in ProxyRequest request) = 0; +// void performProxyGrpcRequest(IAuthCallback callbacks, in ProxyGrpcRequest request) = 1; + void getSpatulaHeader(IAuthCallbacks callbacks) = 2; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyRequest.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyRequest.aidl new file mode 100644 index 00000000..fa5885f8 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth.api.proxy; + +parcelable ProxyRequest; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyResponse.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyResponse.aidl new file mode 100644 index 00000000..eae21f3c --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/api/proxy/ProxyResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.auth.api.proxy; + +parcelable ProxyResponse; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/auth/appcert/IAppCertService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/auth/appcert/IAppCertService.aidl new file mode 100644 index 00000000..ee7f7338 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/auth/appcert/IAppCertService.aidl @@ -0,0 +1,6 @@ +package com.google.android.gms.auth.appcert; + +interface IAppCertService { + boolean fetchDeviceKey() = 0; + String getSpatulaHeader(String packageName) = 1; +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java new file mode 100644 index 00000000..71270ebb --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyRequest.java @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.proxy; + +import android.os.Bundle; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class ProxyRequest extends AutoSafeParcelable { + public static final int HTTP_METHOD_GET = 0; + public static final int HTTP_METHOD_POST = 1; + public static final int HTTP_METHOD_PUT = 2; + public static final int HTTP_METHOD_DELETE = 3; + public static final int HTTP_METHOD_HEAD = 4; + public static final int HTTP_METHOD_OPTIONS = 5; + public static final int HTTP_METHOD_TRACE = 6; + public static final int HTTP_METHOD_PATCH = 7; + + @Field(1000) + private int versionCode = 2; + @Field(1) + public String url; + @Field(2) + public int httpMethod; + @Field(3) + public long timeoutMillis; + @Field(4) + public byte[] body; + @Field(5) + public Bundle headers; + + @Override + public String toString() { + return url; + } + + public static final Creator CREATOR = new AutoCreator<>(ProxyRequest.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java new file mode 100644 index 00000000..951210cb --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/auth/api/proxy/ProxyResponse.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.proxy; + +import android.app.PendingIntent; +import android.os.Bundle; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class ProxyResponse extends AutoSafeParcelable { + public static final int STATUS_CODE_NO_CONNECTION = -1; + + @Field(1000) + private int versionCode = 1; + @Field(1) + public int gmsStatusCode; + @Field(2) + public PendingIntent recoveryAction; + @Field(3) + public int httpStatusCode; + @Field(4) + public Bundle headers; + @Field(5) + public byte[] body; + + public static final Creator CREATOR = new AutoCreator<>(ProxyResponse.class); +} diff --git a/play-services-core-proto/src/main/proto/appcert.proto b/play-services-core-proto/src/main/proto/appcert.proto new file mode 100644 index 00000000..5a9bc815 --- /dev/null +++ b/play-services-core-proto/src/main/proto/appcert.proto @@ -0,0 +1,33 @@ +option java_package = "org.microg.gms.auth.appcert"; +option java_outer_classname = "AppCertProto"; + +message DeviceKeyRequest { + optional string droidGuardResult = 1; + optional uint64 androidId = 2; + optional uint64 sessionId = 3; + message VersionInfo { + optional uint32 sdkVersion = 1; + optional uint32 gmsVersion = 2; + } + optional VersionInfo versionInfo = 4; + optional string token = 5; +} + +message DeviceKey { + optional uint64 keyId = 1; + optional uint64 deviceId = 3; + optional bytes macSecret = 4; + optional bytes keyCert = 5; +} + +message SpatulaHeaderProto { + message PackageInfo { + optional string packageName = 1; + optional string packageCertificateHash = 3; + } + optional PackageInfo packageInfo = 1; + optional bytes hmac = 2; + optional uint64 deviceId = 3; + optional uint64 keyId = 4; + optional bytes keyCert = 5; +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt new file mode 100644 index 00000000..cc38d73c --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertManager.kt @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.appcert + +import android.content.Context +import android.database.Cursor +import android.os.SystemClock +import android.util.Base64 +import android.util.Log +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.Volley +import com.google.android.gms.BuildConfig +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import okio.ByteString.Companion.of +import org.microg.gms.checkin.LastCheckinInfo +import org.microg.gms.common.Constants +import org.microg.gms.common.PackageUtils +import org.microg.gms.droidguard.core.DroidGuardResultCreator +import org.microg.gms.gcm.GcmConstants +import org.microg.gms.gcm.GcmDatabase +import org.microg.gms.gcm.RegisterRequest +import org.microg.gms.gcm.completeRegisterRequest +import org.microg.gms.profile.Build +import org.microg.gms.profile.ProfileManager +import org.microg.gms.settings.SettingsContract.CheckIn +import org.microg.gms.settings.SettingsContract.getSettings +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import kotlin.random.Random + +class AppCertManager(private val context: Context) { + private val queue = Volley.newRequestQueue(context) + + suspend fun fetchDeviceKey(): Boolean { + ProfileManager.ensureInitialized(context) + deviceKeyLock.withLock { + try { + val elapsedRealtime = SystemClock.elapsedRealtime() + if (elapsedRealtime - deviceKeyCacheTime < DEVICE_KEY_TIMEOUT) { + return deviceKey != null + } + Log.w(TAG, "DeviceKeys for app certifications are experimental") + deviceKeyCacheTime = elapsedRealtime + val lastCheckinInfo = LastCheckinInfo.read(context) + val androidId = lastCheckinInfo.androidId + val sessionId = Random.nextLong() + val data = hashMapOf( + "dg_androidId" to androidId.toString(16), + "dg_session" to sessionId.toString(16), + "dg_gmsCoreVersion" to BuildConfig.VERSION_CODE.toString(), + "dg_sdkVersion" to Build.VERSION.SDK_INT.toString() + ) + val droidGuardResult = try { + Base64.encodeToString(DroidGuardResultCreator.getResult(context, "devicekey", data), Base64.NO_WRAP) + } catch (e: Exception) { + null + } + val token = completeRegisterRequest(context, GcmDatabase(context), RegisterRequest().build(context) + .checkin(lastCheckinInfo) + .app("com.google.android.gms", Constants.GMS_PACKAGE_SIGNATURE_SHA1, BuildConfig.VERSION_CODE) + .sender(REGISTER_SENDER) + .extraParam("subscription", REGISTER_SUBSCIPTION) + .extraParam("X-subscription", REGISTER_SUBSCIPTION) + .extraParam("subtype", REGISTER_SUBTYPE) + .extraParam("X-subtype", REGISTER_SUBTYPE) + .extraParam("scope", REGISTER_SCOPE)) + .getString(GcmConstants.EXTRA_REGISTRATION_ID) + val request = DeviceKeyRequest( + droidGuardResult = droidGuardResult, + androidId = lastCheckinInfo.androidId, + sessionId = sessionId, + versionInfo = DeviceKeyRequest.VersionInfo(Build.VERSION.SDK_INT, BuildConfig.VERSION_CODE), + token = token + ) + Log.d(TAG, "Request: ${request.toString().chunked(128).joinToString("\n")}") + val deferredResponse = CompletableDeferred() + queue.add(object : Request(Method.POST, "https://android.googleapis.com/auth/devicekey", null) { + override fun getBody(): ByteArray = request.encode() + + override fun getBodyContentType(): String = "application/octet-stream" + + override fun parseNetworkResponse(response: NetworkResponse): Response { + return if (response.statusCode == 200) { + Response.success(response.data, null) + } else { + Response.success(null, null) + } + } + + override fun deliverError(error: VolleyError) { + Log.d(TAG, "Error: ${Base64.encodeToString(error.networkResponse.data, 2)}") + deferredResponse.complete(null) + } + + override fun deliverResponse(response: ByteArray?) { + deferredResponse.complete(response) + } + + override fun getHeaders(): Map { + return mapOf( + "User-Agent" to "GoogleAuth/1.4 (${Build.DEVICE} ${Build.ID}); gzip", + "content-type" to "application/octet-stream", + "app" to "com.google.android.gms", + "device" to androidId.toString(16) + ) + } + }) + val deviceKeyBytes = deferredResponse.await() ?: return false + deviceKey = DeviceKey.ADAPTER.decode(deviceKeyBytes) + Log.d(TAG, "Response: $deviceKey") + return true + } catch (e: Exception) { + Log.w(TAG, e) + return false + } + } + } + + suspend fun getSpatulaHeader(packageName: String): String? { + val deviceKey = deviceKey ?: if (fetchDeviceKey()) deviceKey else null + val packageCertificateHash = Base64.encodeToString(PackageUtils.firstSignatureDigestBytes(context, packageName), Base64.NO_WRAP) + val proto = if (deviceKey != null) { + val macSecret = deviceKey.macSecret?.toByteArray() + if (macSecret == null) { + Log.w(TAG, "Invalid device key: $deviceKey") + return null + } + val mac = Mac.getInstance("HMACSHA256") + mac.init(SecretKeySpec(macSecret, "HMACSHA256")) + val hmac = mac.doFinal("$packageName$packageCertificateHash".toByteArray()) + SpatulaHeaderProto( + packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash), + hmac = of(*hmac), + deviceId = deviceKey.deviceId, + keyId = deviceKey.keyId, + keyCert = deviceKey.keyCert ?: of() + ) + } else { + Log.d(TAG, "Using fallback spatula header based on Android ID") + val androidId = getSettings(context, CheckIn.getContentUri(context), arrayOf(CheckIn.ANDROID_ID, CheckIn.SECURITY_TOKEN)) { cursor: Cursor -> cursor.getLong(0) } + SpatulaHeaderProto( + packageInfo = SpatulaHeaderProto.PackageInfo(packageName, packageCertificateHash), + deviceId = androidId + ) + } + Log.d(TAG, "Spatula Header: $proto") + return Base64.encodeToString(proto.encode(), Base64.NO_WRAP) + } + + companion object { + private const val TAG = "AppCertManager" + private const val DEVICE_KEY_TIMEOUT = 60 * 60 * 1000L + private const val REGISTER_SENDER = "745476177629" + private const val REGISTER_SUBTYPE = "745476177629" + private const val REGISTER_SUBSCIPTION = "745476177629" + private const val REGISTER_SCOPE = "DeviceKeyRequest" + private val deviceKeyLock = Mutex() + private var deviceKey: DeviceKey? = null + private var deviceKeyCacheTime = 0L + } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt new file mode 100644 index 00000000..e73017e2 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/appcert/AppCertService.kt @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.appcert + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.IBinder +import android.os.Parcel +import android.util.Log +import com.google.android.gms.auth.appcert.IAppCertService +import kotlinx.coroutines.runBlocking +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "AppCertService" + +class AppCertService : Service() { + override fun onBind(intent: Intent): IBinder { + Log.d(TAG, "onBind: $intent") + return AppCertServiceImpl(this).asBinder() + } +} + +class AppCertServiceImpl(private val context: Context) : IAppCertService.Stub() { + private val manager = AppCertManager(context) + + override fun fetchDeviceKey(): Boolean { + PackageUtils.assertExtendedAccess(context) + return runBlocking { manager.fetchDeviceKey() } + } + + override fun getSpatulaHeader(packageName: String): String? { + PackageUtils.assertExtendedAccess(context) + return runBlocking { manager.getSpatulaHeader(packageName) } + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/CredentialPickerActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialPickerActivity.kt similarity index 94% rename from play-services-core/src/main/kotlin/org/microg/gms/auth/CredentialPickerActivity.kt rename to play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialPickerActivity.kt index e8e3fac3..ba1e9c4f 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/CredentialPickerActivity.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialPickerActivity.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2022 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.auth +package org.microg.gms.auth.credentials import android.app.Activity import android.os.Bundle diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/CredentialsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt similarity index 81% rename from play-services-core/src/main/kotlin/org/microg/gms/auth/CredentialsService.kt rename to play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt index 6f571df3..adc0370b 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/CredentialsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-FileCopyrightText: 2022 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ -package org.microg.gms.auth +package org.microg.gms.auth.credentials import android.os.Bundle +import android.os.Parcel import android.util.Log import com.google.android.gms.auth.api.credentials.CredentialRequest import com.google.android.gms.auth.api.credentials.internal.* @@ -15,8 +16,9 @@ import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks import org.microg.gms.BaseService import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues -const val TAG = "GmsCredentials" +private const val TAG = "CredentialService" class CredentialsService : BaseService(TAG, GmsService.CREDENTIALS) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { @@ -50,4 +52,5 @@ class CredentialsServiceImpl : ICredentialsService.Stub() { callbacks.onStatus(Status.SUCCESS) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt new file mode 100644 index 00000000..e1cedd59 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/proxy/AuthProxyService.kt @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.proxy + +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.google.android.gms.auth.api.internal.IAuthCallbacks +import com.google.android.gms.auth.api.internal.IAuthService +import com.google.android.gms.auth.api.proxy.ProxyRequest +import com.google.android.gms.auth.api.proxy.ProxyResponse +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.BaseService +import org.microg.gms.auth.appcert.AppCertManager +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "AuthProxyService" + +class AuthProxyService : BaseService(TAG, GmsService.AUTH_PROXY) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + val consumerPackageName = request.extras.getString("consumerPkg") + if (consumerPackageName != null) PackageUtils.assertExtendedAccess(this) + val serviceImpl = AuthServiceImpl(this, lifecycle, consumerPackageName ?: packageName) + callback.onPostInitComplete(CommonStatusCodes.SUCCESS, serviceImpl, Bundle()) + } +} + +class AuthServiceImpl(private val context: Context, private val lifecycle: Lifecycle, private val packageName: String) : IAuthService.Stub(), LifecycleOwner { + override fun performProxyRequest(callbacks: IAuthCallbacks, request: ProxyRequest) { + Log.d(TAG, "performProxyRequest($packageName, $request)") + lifecycleScope.launchWhenStarted { + callbacks.onProxyResponse(ProxyResponse().apply { gmsStatusCode = CommonStatusCodes.CANCELED }) + } + } + + override fun getSpatulaHeader(callbacks: IAuthCallbacks) { + Log.d(TAG, "getSpatulaHeader($packageName)") + lifecycleScope.launchWhenStarted { + val result = withContext(Dispatchers.IO) { AppCertManager(context).getSpatulaHeader(packageName) } + Log.d(TAG, "Result: $result") + callbacks.onSpatulaHeader(result) + } + } + + override fun getLifecycle(): Lifecycle = lifecycle + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } +} From 552aaf856d9af90f3730bfd31584ae39a2f89c08 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:55:28 +0100 Subject: [PATCH 45/63] Update stub services --- .../internal/IClearcutLoggerCallbacks.aidl | 8 +- .../internal/IClearcutLoggerService.aidl | 8 +- .../clearcut/internal/PlayLoggerContext.aidl | 3 + .../IAppMeasurementDynamiteService.aidl | 55 ++++ .../api/internal/IBundleReceiver.aidl | 5 + .../api/internal/IEventHandlerProxy.aidl | 6 + .../api/internal/IStringProvider.aidl | 4 + .../api/internal/InitializationParams.aidl | 3 + .../internal/IMeasurementService.aidl | 34 +-- .../phenotype/internal/IPhenotypeService.aidl | 1 + .../gms/playlog/internal/IPlayLogService.aidl | 3 +- .../playlog/internal/PlayLoggerContext.aidl | 3 - .../UsageReportingOptInOptions.aidl | 3 + .../internal/IUsageReportingCallbacks.aidl | 11 + ...eReportingOptInOptionsChangedListener.aidl | 5 + .../internal/IUsageReportingService.aidl | 12 + .../gms/clearcut/LogEventParcelable.java | 9 +- .../internal/LogVerifierResultParcelable.java | 15 ++ .../internal/PlayLoggerContext.java | 35 ++- .../api/internal/InitializationParams.java | 45 ++++ .../gms/measurement/internal/AppMetadata.java | 4 + .../gms/measurement/internal/EventParams.java | 17 ++ .../gms/measurement/internal/EventParcel.java | 9 + .../internal/UserAttributeParcel.java | 15 ++ .../gms/playlog/internal/LogEvent.java | 1 + .../UsageReportingOptInOptions.java | 15 ++ .../src/main/AndroidManifest.xml | 255 ++++++++++++++---- .../dynamite/ModuleDescriptor.java | 22 ++ .../gms/clearcut/ClearcutLoggerService.java | 38 --- .../clearcut/ClearcutLoggerServiceImpl.java | 38 --- .../measurement/MeasurementBrokerService.java | 39 --- .../measurement/MeasurementServiceImpl.java | 63 ----- .../microg/gms/playlog/PlayLogService.java | 1 + .../gms/playlog/PlayLogServiceImpl.java | 3 +- .../internal/AppMeasurementDynamiteService.kt | 198 ++++++++++++++ .../gms/clearcut/ClearcutLoggerService.kt | 80 ++++++ .../org/microg/gms/fonts/FontsProvider.kt | 2 +- .../gms/measurement/MeasurementService.kt | 58 ++++ .../microg/gms/phenotype/PhenotypeService.kt | 14 +- .../microg/gms/provision/ProvisionService.kt | 9 +- .../org/microg/gms/udc/FacsCacheService.kt | 3 + .../org/microg/gms/ui/SettingsFragment.kt | 16 +- .../usagereporting/UsageReportingService.kt | 53 ++++ 43 files changed, 924 insertions(+), 297 deletions(-) create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/PlayLoggerContext.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IAppMeasurementDynamiteService.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IBundleReceiver.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IEventHandlerProxy.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IStringProvider.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/InitializationParams.aidl delete mode 100644 play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/PlayLoggerContext.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/usagereporting/UsageReportingOptInOptions.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingCallbacks.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingOptInOptionsChangedListener.aidl create mode 100644 play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingService.aidl create mode 100644 play-services-api/src/main/java/com/google/android/gms/clearcut/internal/LogVerifierResultParcelable.java rename play-services-api/src/main/java/com/google/android/gms/{playlog => clearcut}/internal/PlayLoggerContext.java (77%) create mode 100644 play-services-api/src/main/java/com/google/android/gms/measurement/api/internal/InitializationParams.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParams.java create mode 100644 play-services-api/src/main/java/com/google/android/gms/usagereporting/UsageReportingOptInOptions.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/measurement/dynamite/ModuleDescriptor.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerService.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerServiceImpl.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/measurement/MeasurementBrokerService.java delete mode 100644 play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java create mode 100644 play-services-core/src/main/kotlin/com/google/android/gms/measurement/internal/AppMeasurementDynamiteService.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/clearcut/ClearcutLoggerService.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/measurement/MeasurementService.kt create mode 100644 play-services-core/src/main/kotlin/org/microg/gms/usagereporting/UsageReportingService.kt diff --git a/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerCallbacks.aidl index 31042db8..61e23cff 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerCallbacks.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerCallbacks.aidl @@ -1,7 +1,13 @@ package com.google.android.gms.clearcut.internal; import com.google.android.gms.common.api.Status; +import com.google.android.gms.common.data.DataHolder; interface IClearcutLoggerCallbacks { - oneway void onStatus(in Status status) = 0; + oneway void onLogResult(in Status status) = 0; + oneway void onForceUploadResult(in Status status) = 1; + oneway void onStartCollectForDebugResult(in Status status, long l) = 2; + oneway void onStopCollectForDebugResult(in Status status) = 3; + oneway void onCollectForDebugExpiryTime(in Status status, long l) = 4; + oneway void onLogEventParcelables(in DataHolder data) = 6; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerService.aidl index b2f1b2fd..018252e0 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/IClearcutLoggerService.aidl @@ -4,5 +4,11 @@ import com.google.android.gms.clearcut.internal.IClearcutLoggerCallbacks; import com.google.android.gms.clearcut.LogEventParcelable; interface IClearcutLoggerService { - void log(IClearcutLoggerCallbacks callbacks, in LogEventParcelable event) = 0; + oneway void log(IClearcutLoggerCallbacks callbacks, in LogEventParcelable event) = 0; + oneway void forceUpload(IClearcutLoggerCallbacks callbacks) = 1; + oneway void startCollectForDebug(IClearcutLoggerCallbacks callbacks) = 2; + oneway void stopCollectForDebug(IClearcutLoggerCallbacks callbacks) = 3; + oneway void getCollectForDebugExpiryTime(IClearcutLoggerCallbacks callbacks) = 4; + oneway void getLogEventParcelablesLegacy(IClearcutLoggerCallbacks callbacks) = 5; + oneway void getLogEventParcelables(IClearcutLoggerCallbacks callbacks) = 6; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/PlayLoggerContext.aidl b/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/PlayLoggerContext.aidl new file mode 100644 index 00000000..e213b96c --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/clearcut/internal/PlayLoggerContext.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.clearcut.internal; + +parcelable PlayLoggerContext; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IAppMeasurementDynamiteService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IAppMeasurementDynamiteService.aidl new file mode 100644 index 00000000..5df0a37f --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IAppMeasurementDynamiteService.aidl @@ -0,0 +1,55 @@ +package com.google.android.gms.measurement.api.internal; + +import com.google.android.gms.dynamic.IObjectWrapper; +import com.google.android.gms.measurement.api.internal.IBundleReceiver; +import com.google.android.gms.measurement.api.internal.IEventHandlerProxy; +import com.google.android.gms.measurement.api.internal.IStringProvider; +import com.google.android.gms.measurement.api.internal.InitializationParams; + +interface IAppMeasurementDynamiteService { + void initialize(in IObjectWrapper context, in InitializationParams params, long timestamp) = 0; + void logEvent(String str, String str2, in Bundle bundle, boolean z, boolean z2, long timestamp) = 1; + void logEventAndBundle(String str, String str2, in Bundle bundle, IBundleReceiver receiver, long j) = 2; + void setUserProperty(String str, String str2, in IObjectWrapper obj, boolean z, long j) = 3; + void getUserProperties(String str, String str2, boolean z, IBundleReceiver receiver) = 4; + void getMaxUserProperties(String str, IBundleReceiver receiver) = 5; + void setUserId(String str, long j) = 6; + void setConditionalUserProperty(in Bundle bundle, long j) = 7; + void clearConditionalUserProperty(String str, String str2, in Bundle bundle) = 8; + void getConditionalUserProperties(String str, String str2, IBundleReceiver receiver) = 9; + void setMeasurementEnabled(boolean z, long j) = 10; + void resetAnalyticsData(long j) = 11; + void setMinimumSessionDuration(long j) = 12; + void setSessionTimeoutDuration(long j) = 13; + void setCurrentScreen(in IObjectWrapper obj, String str, String str2, long j) = 14; + void getCurrentScreenName(IBundleReceiver receiver) = 15; + void getCurrentScreenClass(IBundleReceiver receiver) = 16; + void setInstanceIdProvider(IStringProvider provider) = 17; + void getCachedAppInstanceId(IBundleReceiver receiver) = 18; + void getAppInstanceId(IBundleReceiver receiver) = 19; + void getGmpAppId(IBundleReceiver receiver) = 20; + void generateEventId(IBundleReceiver receiver) = 21; + void beginAdUnitExposure(String str, long j) = 22; + void endAdUnitExposure(String str, long j) = 23; + void onActivityStarted(in IObjectWrapper activity, long j) = 24; + void onActivityStopped(in IObjectWrapper activity, long j) = 25; + void onActivityCreated(in IObjectWrapper activity, in Bundle bundle, long j) = 26; + void onActivityDestroyed(in IObjectWrapper activity, long j) = 27; + void onActivityPaused(in IObjectWrapper activity, long j) = 28; + void onActivityResumed(in IObjectWrapper activity, long j) = 29; + void onActivitySaveInstanceState(in IObjectWrapper activity, IBundleReceiver receiver, long j) = 30; + void performAction(in Bundle bundle, IBundleReceiver receiver, long j) = 31; + void logHealthData(int i, String str, in IObjectWrapper obj, in IObjectWrapper obj2, in IObjectWrapper obj3) = 32; + void setEventInterceptor(IEventHandlerProxy proxy) = 33; + void registerOnMeasurementEventListener(IEventHandlerProxy proxy) = 34; + void unregisterOnMeasurementEventListener(IEventHandlerProxy proxy) = 35; + void initForTests(in Map map) = 36; + void getTestFlag(IBundleReceiver receiver, int i) = 37; + void setDataCollectionEnabled(boolean z) = 38; + void isDataCollectionEnabled(IBundleReceiver receiver) = 39; + + void setDefaultEventParameters(in Bundle bundle) = 41; + void setConsent(in Bundle bundle, long j) = 43; + void setConsentThirdParty(in Bundle bundle, long j) = 44; + void clearMeasurementEnabled(long j) = 42; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IBundleReceiver.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IBundleReceiver.aidl new file mode 100644 index 00000000..68d80ee8 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IBundleReceiver.aidl @@ -0,0 +1,5 @@ +package com.google.android.gms.measurement.api.internal; + +interface IBundleReceiver { + void onBundle(in Bundle bundle); +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IEventHandlerProxy.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IEventHandlerProxy.aidl new file mode 100644 index 00000000..5325899b --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IEventHandlerProxy.aidl @@ -0,0 +1,6 @@ +package com.google.android.gms.measurement.api.internal; + +interface IEventHandlerProxy { + void f1(String s1, String s2, in Bundle bundle, long j) = 0; + int f2() = 1; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IStringProvider.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IStringProvider.aidl new file mode 100644 index 00000000..2d8f9d17 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/IStringProvider.aidl @@ -0,0 +1,4 @@ +package com.google.android.gms.measurement.api.internal; + +interface IStringProvider { +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/InitializationParams.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/InitializationParams.aidl new file mode 100644 index 00000000..cce367c7 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/api/internal/InitializationParams.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.measurement.api.internal; + +parcelable InitializationParams; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl index 8a99173a..1c78896a 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/measurement/internal/IMeasurementService.aidl @@ -3,23 +3,25 @@ package com.google.android.gms.measurement.internal; import com.google.android.gms.measurement.internal.AppMetadata; import com.google.android.gms.measurement.internal.ConditionalUserPropertyParcel; import com.google.android.gms.measurement.internal.EventParcel; +import com.google.android.gms.measurement.internal.UserAttributeParcel; interface IMeasurementService { - void f1(in EventParcel p0, in AppMetadata p1) = 0; -// void zza(UserAttributeParcel p0, AppMetadata p1) = 1; - void f4(in AppMetadata p0) = 3; -// void zza(EventParcel p0, String p1, String p2) = 4; -// void zzb(AppMetadata p0) = 5; -// List zza(AppMetadata p0, boolean p1) = 6; -// byte[] zza(EventParcel p0, String p1) = 8; + void f1(in EventParcel event, in AppMetadata app) = 0; + void f2(in UserAttributeParcel attribute, in AppMetadata app) = 1; + void f4(in AppMetadata app) = 3; +// void f5(in EventParcel p0, String p1, String p2) = 4; +// void f6(in AppMetadata p0) = 5; +// List f7(in AppMetadata p0, boolean p1) = 6; +// byte[] f9(in EventParcel p0, String p1) = 8; void f10(long p0, String p1, String p2, String p3) = 9; - String f11(in AppMetadata p0) = 10; - void f12(in ConditionalUserPropertyParcel p0, in AppMetadata p1) = 11; -// void zza(ConditionalUserPropertyParcel p0) = 12; -// List zza(String p0, String p1, boolean p2, AppMetadata p3) = 13; -// List zza(String p0, String p1, String p2, boolean p3) = 14; -// List zza(String p0, String p1, AppMetadata p2) = 15; -// List zza(String p0, String p1, String p2) = 16; -// void zzd(AppMetadata p0) = 17; -// void zza(Bundle p0, AppMetadata p1) = 18; + String f11(in AppMetadata app) = 10; + void f12(in ConditionalUserPropertyParcel property, in AppMetadata app) = 11; +// void f13(ConditionalUserPropertyParcel p0) = 12; +// List getUserProperties(String p0, String p1, boolean p2, in AppMetadata p3) = 13; +// List getUserPropertiesAs(String p0, String p1, String p2, boolean p3) = 14; +// List getConditionalUserProperties(String p0, String p1, in AppMetadata p2) = 15; +// List getCondtionalUserPropertiesAs(String p0, String p1, String p2) = 16; +// void f18(in AppMetadata p0) = 17; + void setDefaultEventParameters(in Bundle params, in AppMetadata app) = 18; +// void f20(in AppMetadata p0) = 19; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl index 724969f9..abba647d 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/phenotype/internal/IPhenotypeService.aidl @@ -4,5 +4,6 @@ import com.google.android.gms.phenotype.internal.IPhenotypeCallbacks; interface IPhenotypeService { void register(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in byte[] p4) = 0; + void register2(IPhenotypeCallbacks callbacks, String p1, int p2, in String[] p3, in int[] p4, in byte[] p5) = 1; void getConfigurationSnapshot(IPhenotypeCallbacks callbacks, String p1, String p2, String p3) = 10; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/IPlayLogService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/IPlayLogService.aidl index 86169b99..9bcdee44 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/IPlayLogService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/IPlayLogService.aidl @@ -1,8 +1,9 @@ package com.google.android.gms.playlog.internal; +import com.google.android.gms.clearcut.internal.PlayLoggerContext; import com.google.android.gms.playlog.internal.LogEvent; -import com.google.android.gms.playlog.internal.PlayLoggerContext; +// Deprecated interface IPlayLogService { void onEvent(String packageName, in PlayLoggerContext context, in LogEvent event) = 1; void onMultiEvent(String packageName, in PlayLoggerContext context, in List events) = 2; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/PlayLoggerContext.aidl b/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/PlayLoggerContext.aidl deleted file mode 100644 index 9cc0e0e4..00000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/playlog/internal/PlayLoggerContext.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package com.google.android.gms.playlog.internal; - -parcelable PlayLoggerContext; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/UsageReportingOptInOptions.aidl b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/UsageReportingOptInOptions.aidl new file mode 100644 index 00000000..a88060fb --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/UsageReportingOptInOptions.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.usagereporting; + +parcelable UsageReportingOptInOptions; diff --git a/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingCallbacks.aidl b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingCallbacks.aidl new file mode 100644 index 00000000..3e877c25 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingCallbacks.aidl @@ -0,0 +1,11 @@ +package com.google.android.gms.usagereporting.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.usagereporting.UsageReportingOptInOptions; + +interface IUsageReportingCallbacks { + oneway void onOptInOptions(in Status status, in UsageReportingOptInOptions options) = 1; + oneway void onOptInOptionsSet(in Status status) = 2; + oneway void onOptInOptionsChangedListenerAdded(in Status status) = 3; + oneway void onOptInOptionsChangedListenerRemoved(in Status status) = 4; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingOptInOptionsChangedListener.aidl b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingOptInOptionsChangedListener.aidl new file mode 100644 index 00000000..8e9c2875 --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingOptInOptionsChangedListener.aidl @@ -0,0 +1,5 @@ +package com.google.android.gms.usagereporting.internal; + +interface IUsageReportingOptInOptionsChangedListener { + oneway void onOptionsChanged() = 1; +} diff --git a/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingService.aidl b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingService.aidl new file mode 100644 index 00000000..46d6265d --- /dev/null +++ b/play-services-api/src/main/aidl/com/google/android/gms/usagereporting/internal/IUsageReportingService.aidl @@ -0,0 +1,12 @@ +package com.google.android.gms.usagereporting.internal; + +import com.google.android.gms.usagereporting.internal.IUsageReportingCallbacks; +import com.google.android.gms.usagereporting.internal.IUsageReportingOptInOptionsChangedListener; +import com.google.android.gms.usagereporting.UsageReportingOptInOptions; + +interface IUsageReportingService { + oneway void getOptInOptions(IUsageReportingCallbacks callbacks) = 1; + oneway void setOptInOptions(in UsageReportingOptInOptions options, IUsageReportingCallbacks callbacks) = 2; + oneway void addOptInOptionsChangedListener(IUsageReportingOptInOptionsChangedListener listener, IUsageReportingCallbacks callbacks) = 3; + oneway void removeOptInOptionsChangedListener(IUsageReportingOptInOptionsChangedListener listener, IUsageReportingCallbacks callbacks) = 4; +} diff --git a/play-services-api/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java b/play-services-api/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java index 6ff57373..0f193b9c 100644 --- a/play-services-api/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java +++ b/play-services-api/src/main/java/com/google/android/gms/clearcut/LogEventParcelable.java @@ -18,12 +18,12 @@ package com.google.android.gms.clearcut; import android.util.Base64; +import com.google.android.gms.clearcut.internal.LogVerifierResultParcelable; import com.google.android.gms.phenotype.ExperimentToken; import com.google.android.gms.phenotype.GenericDimension; -import com.google.android.gms.playlog.internal.PlayLoggerContext; +import com.google.android.gms.clearcut.internal.PlayLoggerContext; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -62,6 +62,9 @@ public class LogEventParcelable extends AutoSafeParcelable { @Field(10) public final GenericDimension[] genericDimensions; + @Field(11) + public final LogVerifierResultParcelable logVerifierResult; + private LogEventParcelable() { context = null; bytes = null; @@ -71,6 +74,7 @@ public class LogEventParcelable extends AutoSafeParcelable { addPhenotypeExperimentTokens = false; experimentTokenParcelables = null; genericDimensions = null; + logVerifierResult = null; } public LogEventParcelable(PlayLoggerContext context, byte[] bytes, int[] testCodes, String[] mendelPackages, int[] experimentIds, byte[][] experimentTokens, boolean addPhenotypeExperimentTokens) { @@ -83,6 +87,7 @@ public class LogEventParcelable extends AutoSafeParcelable { this.addPhenotypeExperimentTokens = addPhenotypeExperimentTokens; this.experimentTokenParcelables = null; this.genericDimensions = null; + this.logVerifierResult = null; } @Override diff --git a/play-services-api/src/main/java/com/google/android/gms/clearcut/internal/LogVerifierResultParcelable.java b/play-services-api/src/main/java/com/google/android/gms/clearcut/internal/LogVerifierResultParcelable.java new file mode 100644 index 00000000..4b1fa834 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/clearcut/internal/LogVerifierResultParcelable.java @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.clearcut.internal; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class LogVerifierResultParcelable extends AutoSafeParcelable { + @Field(1) + public boolean b; + + public static final Creator CREATOR = new AutoCreator<>(LogVerifierResultParcelable.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/playlog/internal/PlayLoggerContext.java b/play-services-api/src/main/java/com/google/android/gms/clearcut/internal/PlayLoggerContext.java similarity index 77% rename from play-services-api/src/main/java/com/google/android/gms/playlog/internal/PlayLoggerContext.java rename to play-services-api/src/main/java/com/google/android/gms/clearcut/internal/PlayLoggerContext.java index 7f793740..0827632d 100644 --- a/play-services-api/src/main/java/com/google/android/gms/playlog/internal/PlayLoggerContext.java +++ b/play-services-api/src/main/java/com/google/android/gms/clearcut/internal/PlayLoggerContext.java @@ -1,23 +1,11 @@ /* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ -package com.google.android.gms.playlog.internal; +package com.google.android.gms.clearcut.internal; import org.microg.safeparcel.AutoSafeParcelable; -import org.microg.safeparcel.SafeParceled; public class PlayLoggerContext extends AutoSafeParcelable { @@ -33,9 +21,6 @@ public class PlayLoggerContext extends AutoSafeParcelable { @Field(4) public final int logSource; - @Field(8) - public final String logSourceName; - @Field(5) public final String uploadAccount; @@ -45,6 +30,9 @@ public class PlayLoggerContext extends AutoSafeParcelable { @Field(7) public final boolean logAndroidId; + @Field(8) + public final String logSourceName; + @Field(9) public final boolean isAnonymous; @@ -57,13 +45,16 @@ public class PlayLoggerContext extends AutoSafeParcelable { @Field(12) public final boolean scrubMccMnc; + @Field(13) + public final Integer piiLevelset; + private PlayLoggerContext() { packageName = uploadAccount = logSourceName = loggingId = null; - qosTier = packageVersionCode = logSource = appMobileSpecId = -1; + qosTier = packageVersionCode = logSource = appMobileSpecId = piiLevelset = -1; isAnonymous = logAndroidId = scrubMccMnc = false; } - public PlayLoggerContext(String packageName, int packageVersionCode, int logSource, String logSourceName, String uploadAccount, String loggingId, boolean isAnonymous, int qosTier, boolean scrubMccMnc) { + public PlayLoggerContext(String packageName, int packageVersionCode, int logSource, String logSourceName, String uploadAccount, String loggingId, boolean isAnonymous, int qosTier, boolean scrubMccMnc, int piiLevelset) { this.packageName = packageName; this.packageVersionCode = packageVersionCode; this.logSource = logSource; @@ -75,6 +66,7 @@ public class PlayLoggerContext extends AutoSafeParcelable { this.qosTier = qosTier; this.appMobileSpecId = null; this.scrubMccMnc = scrubMccMnc; + this.piiLevelset = piiLevelset; } @Override @@ -89,6 +81,9 @@ public class PlayLoggerContext extends AutoSafeParcelable { sb.append(", logSourceName=").append(logSourceName); sb.append(", isAnonymous=").append(isAnonymous); sb.append(", qosTier=").append(qosTier); + sb.append(", appMobileSpecId=").append(appMobileSpecId); + sb.append(", scrubMccMnc=").append(scrubMccMnc); + sb.append(", piiLevelset=").append(piiLevelset); sb.append(']'); return sb.toString(); } diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/api/internal/InitializationParams.java b/play-services-api/src/main/java/com/google/android/gms/measurement/api/internal/InitializationParams.java new file mode 100644 index 00000000..43d7ff75 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/api/internal/InitializationParams.java @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.measurement.api.internal; + +import android.os.Bundle; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class InitializationParams extends AutoSafeParcelable { + @Field(1) + public long field1; + @Field(2) + public long field2; + @Field(3) + public boolean field3; + @Field(4) + public String field4; + @Field(5) + public String field5; + @Field(6) + public String field6; + @Field(7) + public Bundle field7; + @Field(8) + public String field8; + + @Override + public String toString() { + return "InitializationParams{" + + "field1=" + field1 + + ", field2=" + field2 + + ", field3=" + field3 + + ", field4='" + field4 + '\'' + + ", field5='" + field5 + '\'' + + ", field6='" + field6 + '\'' + + ", field7=" + field7 + + ", field8='" + field8 + '\'' + + '}'; + } + + public static final Creator CREATOR = new AutoCreator<>(InitializationParams.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java index be233570..9c7c3671 100644 --- a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/AppMetadata.java @@ -57,5 +57,9 @@ public class AppMetadata extends AutoSafeParcelable { @Field(25) private String field25; + public String toString() { + return "AppMetadata[" + packageName + "]"; + } + public static final Creator CREATOR = new AutoCreator<>(AppMetadata.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParams.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParams.java new file mode 100644 index 00000000..54de1394 --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParams.java @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2020, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.measurement.internal; + +import android.os.Bundle; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class EventParams extends AutoSafeParcelable { + @Field(2) + public Bundle data; + + public static final Creator CREATOR = new AutoCreator<>(EventParams.class); +} diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java index 3ff7c796..7b5b6179 100644 --- a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/EventParcel.java @@ -8,5 +8,14 @@ package com.google.android.gms.measurement.internal; import org.microg.safeparcel.AutoSafeParcelable; public class EventParcel extends AutoSafeParcelable { + @Field(2) + public String name; + @Field(3) + public EventParams params; + @Field(4) + public String origin; + @Field(5) + public long timestamp; + public static final Creator CREATOR = new AutoCreator<>(EventParcel.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java index 6070b923..d9c74663 100644 --- a/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java +++ b/play-services-api/src/main/java/com/google/android/gms/measurement/internal/UserAttributeParcel.java @@ -8,5 +8,20 @@ package com.google.android.gms.measurement.internal; import org.microg.safeparcel.AutoSafeParcelable; public class UserAttributeParcel extends AutoSafeParcelable { + @Field(1) + public int field1; + @Field(2) + public String name; + @Field(3) + public long timestamp; + @Field(4) + public Long field4; + @Field(6) + public String field6; + @Field(7) + public String field7; + @Field(8) + public Double field8; + public static final Creator CREATOR = new AutoCreator<>(UserAttributeParcel.class); } diff --git a/play-services-api/src/main/java/com/google/android/gms/playlog/internal/LogEvent.java b/play-services-api/src/main/java/com/google/android/gms/playlog/internal/LogEvent.java index 791c19b8..03ccbbc5 100644 --- a/play-services-api/src/main/java/com/google/android/gms/playlog/internal/LogEvent.java +++ b/play-services-api/src/main/java/com/google/android/gms/playlog/internal/LogEvent.java @@ -21,6 +21,7 @@ import org.microg.safeparcel.SafeParceled; import java.util.Date; +@Deprecated public class LogEvent extends AutoSafeParcelable { @SafeParceled(1) diff --git a/play-services-api/src/main/java/com/google/android/gms/usagereporting/UsageReportingOptInOptions.java b/play-services-api/src/main/java/com/google/android/gms/usagereporting/UsageReportingOptInOptions.java new file mode 100644 index 00000000..0e7ec10b --- /dev/null +++ b/play-services-api/src/main/java/com/google/android/gms/usagereporting/UsageReportingOptInOptions.java @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.usagereporting; + +import org.microg.safeparcel.AutoSafeParcelable; + +public class UsageReportingOptInOptions extends AutoSafeParcelable { + @Field(2) + public int optInUsageReporting; + + public static final Creator CREATOR = new AutoCreator<>(UsageReportingOptInOptions.class); +} diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index e4fa697d..0649e1bb 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -112,15 +112,15 @@ - + - - - - - - - - - - - - @@ -415,7 +401,7 @@ android:exported="true" /> @@ -423,12 +409,24 @@ - + + + + + + + + + + + + + @@ -466,7 +464,7 @@ + android:exported="true" /> + android:roundIcon="@mipmap/ic_microg_settings" + android:taskAffinity="org.microg.gms.settings"> + android:roundIcon="@mipmap/ic_microg_settings" + android:taskAffinity="org.microg.gms.settings" /> + android:targetActivity="org.microg.gms.ui.SettingsActivity" + android:taskAffinity="org.microg.gms.settings"> @@ -532,26 +533,25 @@ + android:process=":ui" + android:taskAffinity="org.microg.gms.settings" /> - - + android:process=":ui" + android:taskAffinity="org.microg.gms.settings" /> + android:process=":ui" + android:taskAffinity="org.microg.gms.settings" /> + android:process=":ui" + android:taskAffinity="org.microg.gms.settings"> @@ -564,7 +564,8 @@ + android:process=":ui" + android:taskAffinity="org.microg.gms.settings"> @@ -590,7 +591,7 @@ @@ -705,37 +706,173 @@ + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/play-services-core/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/measurement/dynamite/ModuleDescriptor.java b/play-services-core/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/measurement/dynamite/ModuleDescriptor.java new file mode 100644 index 00000000..0c3f0bcd --- /dev/null +++ b/play-services-core/src/main/java/com/google/android/gms/dynamite/descriptors/com/google/android/gms/measurement/dynamite/ModuleDescriptor.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2013-2017 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * 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. + */ + +package com.google.android.gms.dynamite.descriptors.com.google.android.gms.measurement.dynamite; + +public class ModuleDescriptor { + public static final String MODULE_ID = "com.google.android.gms.measurement.dynamite"; + public static final int MODULE_VERSION = 53; +} diff --git a/play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerService.java b/play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerService.java deleted file mode 100644 index 02203153..00000000 --- a/play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerService.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package org.microg.gms.clearcut; - -import android.os.RemoteException; - -import com.google.android.gms.common.internal.GetServiceRequest; -import com.google.android.gms.common.internal.IGmsCallbacks; - -import org.microg.gms.BaseService; -import org.microg.gms.common.GmsService; - -public class ClearcutLoggerService extends BaseService { - private ClearcutLoggerServiceImpl clearcutService = new ClearcutLoggerServiceImpl(); - - public ClearcutLoggerService() { - super("GmsClearcutSvc", GmsService.CLEARCUT_LOGGER); - } - - @Override - public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException { - callback.onPostInitComplete(0, clearcutService.asBinder(), null); - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerServiceImpl.java deleted file mode 100644 index fd1a1dac..00000000 --- a/play-services-core/src/main/java/org/microg/gms/clearcut/ClearcutLoggerServiceImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package org.microg.gms.clearcut; - -import android.os.RemoteException; -import android.util.Log; - -import com.google.android.gms.clearcut.LogEventParcelable; -import com.google.android.gms.clearcut.internal.IClearcutLoggerCallbacks; -import com.google.android.gms.clearcut.internal.IClearcutLoggerService; -import com.google.android.gms.common.api.Status; - -public class ClearcutLoggerServiceImpl extends IClearcutLoggerService.Stub { - private static final String TAG = "GmsClearcutLogSvcImpl"; - - @Override - public void log(IClearcutLoggerCallbacks callbacks, LogEventParcelable event) throws RemoteException { - // These logs are not really helpful for us, so let's just ignore it. - try { - callbacks.onStatus(Status.SUCCESS); - } catch (Exception ignored) { - } - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementBrokerService.java b/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementBrokerService.java deleted file mode 100644 index 4cd9a0f8..00000000 --- a/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementBrokerService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2018 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package org.microg.gms.measurement; - -import android.os.RemoteException; -import android.util.Log; - -import com.google.android.gms.common.api.CommonStatusCodes; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.common.internal.GetServiceRequest; -import com.google.android.gms.common.internal.IGmsCallbacks; - -import org.microg.gms.BaseService; -import org.microg.gms.common.GmsService; - -public class MeasurementBrokerService extends BaseService { - public MeasurementBrokerService() { - super("GmsMeasureBrokerSvc", GmsService.MEASUREMENT); - } - - @Override - public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException { - callback.onPostInitComplete(CommonStatusCodes.SUCCESS, new MeasurementServiceImpl().asBinder(), null); - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java deleted file mode 100644 index b6fbb62b..00000000 --- a/play-services-core/src/main/java/org/microg/gms/measurement/MeasurementServiceImpl.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2018 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ - -package org.microg.gms.measurement; - -import android.os.Parcel; -import android.os.RemoteException; -import android.util.Log; - -import com.google.android.gms.measurement.internal.AppMetadata; -import com.google.android.gms.measurement.internal.ConditionalUserPropertyParcel; -import com.google.android.gms.measurement.internal.EventParcel; -import com.google.android.gms.measurement.internal.IMeasurementService; - -public class MeasurementServiceImpl extends IMeasurementService.Stub { - private static final String TAG = "GmsMeasureSvcImpl"; - - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { - if (super.onTransact(code, data, reply, flags)) return true; - Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags); - return false; - } - - @Override - public void f1(EventParcel p0, AppMetadata p1) throws RemoteException { - Log.d(TAG, "f1: " + p1.packageName); - } - - @Override - public void f4(AppMetadata p0) throws RemoteException { - Log.d(TAG, "f4: " + p0.packageName); - } - - @Override - public void f10(long p0, String p1, String p2, String p3) throws RemoteException { - Log.d(TAG, "f10: " + p1); - } - - @Override - public String f11(AppMetadata p0) throws RemoteException { - Log.d(TAG, "f11: " + p0.packageName); - return null; - } - - @Override - public void f12(ConditionalUserPropertyParcel p0, AppMetadata p1) throws RemoteException { - Log.d(TAG, "f12: " + p1.packageName); - } -} diff --git a/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogService.java b/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogService.java index 28015345..f86cd109 100644 --- a/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogService.java +++ b/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogService.java @@ -24,6 +24,7 @@ import com.google.android.gms.common.internal.IGmsCallbacks; import org.microg.gms.BaseService; import org.microg.gms.common.GmsService; +@Deprecated public class PlayLogService extends BaseService { private PlayLogServiceImpl playLogService = new PlayLogServiceImpl(); diff --git a/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogServiceImpl.java index ddf82e44..62996728 100644 --- a/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/playlog/PlayLogServiceImpl.java @@ -22,10 +22,11 @@ import android.util.Log; import com.google.android.gms.playlog.internal.IPlayLogService; import com.google.android.gms.playlog.internal.LogEvent; -import com.google.android.gms.playlog.internal.PlayLoggerContext; +import com.google.android.gms.clearcut.internal.PlayLoggerContext; import java.util.List; +@Deprecated public class PlayLogServiceImpl extends IPlayLogService.Stub { private static final String TAG = "GmsPlayLogSvcImpl"; diff --git a/play-services-core/src/main/kotlin/com/google/android/gms/measurement/internal/AppMeasurementDynamiteService.kt b/play-services-core/src/main/kotlin/com/google/android/gms/measurement/internal/AppMeasurementDynamiteService.kt new file mode 100644 index 00000000..a0f3373c --- /dev/null +++ b/play-services-core/src/main/kotlin/com/google/android/gms/measurement/internal/AppMeasurementDynamiteService.kt @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package com.google.android.gms.measurement.internal + +import android.os.Bundle +import android.os.Parcelable +import android.util.Log +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.measurement.api.internal.* + +private const val TAG = "AppMeasurementService" + +class AppMeasurementDynamiteService : IAppMeasurementDynamiteService.Stub() { + override fun initialize(context: IObjectWrapper?, params: InitializationParams?, timestamp: Long) { + Log.d(TAG, "Not yet implemented: initialize") + } + + override fun logEvent(str: String?, str2: String?, bundle: Bundle?, z: Boolean, z2: Boolean, timestamp: Long) { + Log.d(TAG, "Not yet implemented: logEvent") + } + + override fun logEventAndBundle(str: String?, str2: String?, bundle: Bundle?, receiver: IBundleReceiver?, j: Long) { + Log.d(TAG, "Not yet implemented: logEventAndBundle") + receiver?.onBundle(Bundle().apply { putByteArray("r", ByteArray(0)) }) + } + + override fun setUserProperty(str: String?, str2: String?, obj: IObjectWrapper?, z: Boolean, j: Long) { + Log.d(TAG, "Not yet implemented: setUserProperty") + } + + override fun getUserProperties(str: String?, str2: String?, z: Boolean, receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getUserProperties") + receiver?.onBundle(Bundle()) + } + + override fun getMaxUserProperties(str: String?, receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getMaxUserProperties") + receiver?.onBundle(Bundle().apply { putInt("r", 25) }) + } + + override fun setUserId(str: String?, j: Long) { + Log.d(TAG, "Not yet implemented: setUserId") + } + + override fun setConditionalUserProperty(bundle: Bundle?, j: Long) { + Log.d(TAG, "Not yet implemented: setConditionalUserProperty") + } + + override fun clearConditionalUserProperty(str: String?, str2: String?, bundle: Bundle?) { + Log.d(TAG, "Not yet implemented: clearConditionalUserProperty") + } + + override fun getConditionalUserProperties(str: String?, str2: String?, receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getConditionalUserProperties") + receiver?.onBundle(Bundle().apply { putParcelableArrayList("r", arrayListOf()) }) + } + + override fun setMeasurementEnabled(z: Boolean, j: Long) { + Log.d(TAG, "Not yet implemented: setMeasurementEnabled") + } + + override fun resetAnalyticsData(j: Long) { + Log.d(TAG, "Not yet implemented: resetAnalyticsData") + } + + override fun setMinimumSessionDuration(j: Long) { + Log.d(TAG, "Not yet implemented: setMinimumSessionDuration") + } + + override fun setSessionTimeoutDuration(j: Long) { + Log.d(TAG, "Not yet implemented: setSessionTimeoutDuration") + } + + override fun setCurrentScreen(obj: IObjectWrapper?, str: String?, str2: String?, j: Long) { + Log.d(TAG, "Not yet implemented: setCurrentScreen") + } + + override fun getCurrentScreenName(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getCurrentScreenName") + } + + override fun getCurrentScreenClass(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getCurrentScreenClass") + } + + override fun setInstanceIdProvider(provider: IStringProvider?) { + Log.d(TAG, "Not yet implemented: setInstanceIdProvider") + } + + override fun getCachedAppInstanceId(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getCachedAppInstanceId") + } + + override fun getAppInstanceId(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getAppInstanceId") + } + + override fun getGmpAppId(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: getGmpAppId") + } + + override fun generateEventId(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: generateEventId") + } + + override fun beginAdUnitExposure(str: String?, j: Long) { + Log.d(TAG, "Not yet implemented: beginAdUnitExposure") + } + + override fun endAdUnitExposure(str: String?, j: Long) { + Log.d(TAG, "Not yet implemented: endAdUnitExposure") + } + + override fun onActivityStarted(activity: IObjectWrapper?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivityStarted") + } + + override fun onActivityStopped(activity: IObjectWrapper?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivityStopped") + } + + override fun onActivityCreated(activity: IObjectWrapper?, bundle: Bundle?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivityCreated") + } + + override fun onActivityDestroyed(activity: IObjectWrapper?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivityDestroyed") + } + + override fun onActivityPaused(activity: IObjectWrapper?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivityPaused") + } + + override fun onActivityResumed(activity: IObjectWrapper?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivityResumed") + } + + override fun onActivitySaveInstanceState(activity: IObjectWrapper?, receiver: IBundleReceiver?, j: Long) { + Log.d(TAG, "Not yet implemented: onActivitySaveInstanceState") + receiver?.onBundle(Bundle()) + } + + override fun performAction(bundle: Bundle?, receiver: IBundleReceiver?, j: Long) { + Log.d(TAG, "Not yet implemented: performAction") + } + + override fun logHealthData(i: Int, str: String?, obj: IObjectWrapper?, obj2: IObjectWrapper?, obj3: IObjectWrapper?) { + Log.d(TAG, "Not yet implemented: logHealthData") + } + + override fun setEventInterceptor(proxy: IEventHandlerProxy?) { + Log.d(TAG, "Not yet implemented: setEventInterceptor") + } + + override fun registerOnMeasurementEventListener(proxy: IEventHandlerProxy?) { + Log.d(TAG, "Not yet implemented: registerOnMeasurementEventListener") + } + + override fun unregisterOnMeasurementEventListener(proxy: IEventHandlerProxy?) { + Log.d(TAG, "Not yet implemented: unregisterOnMeasurementEventListener") + } + + override fun initForTests(map: MutableMap?) { + Log.d(TAG, "Not yet implemented: initForTests") + } + + override fun getTestFlag(receiver: IBundleReceiver?, i: Int) { + Log.d(TAG, "Not yet implemented: getTestFlag") + } + + override fun setDataCollectionEnabled(z: Boolean) { + Log.d(TAG, "Not yet implemented: setDataCollectionEnabled") + } + + override fun isDataCollectionEnabled(receiver: IBundleReceiver?) { + Log.d(TAG, "Not yet implemented: isDataCollectionEnabled") + receiver?.onBundle(Bundle().apply { putBoolean("r", false) }) + } + + override fun setDefaultEventParameters(bundle: Bundle?) { + Log.d(TAG, "Not yet implemented: setDefaultEventParameters") + } + + override fun setConsent(bundle: Bundle?, j: Long) { + Log.d(TAG, "Not yet implemented: setConsent") + } + + override fun setConsentThirdParty(bundle: Bundle?, j: Long) { + Log.d(TAG, "Not yet implemented: setConsentThirdParty") + } + + override fun clearMeasurementEnabled(j: Long) { + Log.d(TAG, "Not yet implemented: clearMeasurementEnabled") + } + +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/clearcut/ClearcutLoggerService.kt b/play-services-core/src/main/kotlin/org/microg/gms/clearcut/ClearcutLoggerService.kt new file mode 100644 index 00000000..afe0dc7f --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/clearcut/ClearcutLoggerService.kt @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ +package org.microg.gms.clearcut + +import android.os.Parcel +import android.os.RemoteException +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.clearcut.LogEventParcelable +import com.google.android.gms.clearcut.internal.IClearcutLoggerCallbacks +import com.google.android.gms.clearcut.internal.IClearcutLoggerService +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.data.DataHolder +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "ClearcutLoggerService" +private const val COLLECT_FOR_DEBUG_DURATION = 24L * 60 * 60 * 1000 + +class ClearcutLoggerService : BaseService(TAG, GmsService.CLEARCUT_LOGGER) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete(0, ClearcutLoggerServiceImpl(lifecycle), null) + } +} + +class ClearcutLoggerServiceImpl(private val lifecycle: Lifecycle) : IClearcutLoggerService.Stub(), LifecycleOwner { + private var collectForDebugExpiryTime: Long = 0 + + override fun log(callbacks: IClearcutLoggerCallbacks, event: LogEventParcelable) { + lifecycleScope.launchWhenStarted { + callbacks.onLogResult(Status.SUCCESS) + } + } + + override fun forceUpload(callbacks: IClearcutLoggerCallbacks) { + lifecycleScope.launchWhenStarted { + callbacks.onLogResult(Status.SUCCESS) + } + } + + override fun startCollectForDebug(callbacks: IClearcutLoggerCallbacks) { + lifecycleScope.launchWhenStarted { + collectForDebugExpiryTime = System.currentTimeMillis() + COLLECT_FOR_DEBUG_DURATION + callbacks.onStartCollectForDebugResult(Status.SUCCESS, collectForDebugExpiryTime) + } + } + + override fun stopCollectForDebug(callbacks: IClearcutLoggerCallbacks) { + lifecycleScope.launchWhenStarted { + callbacks.onStopCollectForDebugResult(Status.SUCCESS) + } + } + + override fun getCollectForDebugExpiryTime(callbacks: IClearcutLoggerCallbacks) { + lifecycleScope.launchWhenStarted { + callbacks.onCollectForDebugExpiryTime(Status.SUCCESS, collectForDebugExpiryTime) + } + } + + override fun getLogEventParcelablesLegacy(callbacks: IClearcutLoggerCallbacks) { + getLogEventParcelables(callbacks) + } + + override fun getLogEventParcelables(callbacks: IClearcutLoggerCallbacks) { + lifecycleScope.launchWhenStarted { + callbacks.onLogEventParcelables(DataHolder.empty(CommonStatusCodes.SUCCESS)) + } + } + + override fun getLifecycle(): Lifecycle = lifecycle + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/fonts/FontsProvider.kt b/play-services-core/src/main/kotlin/org/microg/gms/fonts/FontsProvider.kt index a15180c5..2ccf28d0 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/fonts/FontsProvider.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/fonts/FontsProvider.kt @@ -35,7 +35,7 @@ class FontsProvider : ContentProvider() { selectionArgs: Array?, sortOrder: String? ): Cursor { - Log.e(TAG, "query: $uri ${projection?.toList()} $selection") + Log.e(TAG, "query: $uri ${projection?.toList()} $selection ${selectionArgs?.joinToString(prefix = "[", postfix = "]")}") val cursor = MatrixCursor(COLUMNS) // We could also return an empty cursor here, but some apps have been reported to crash // when their expected font is not returned by Google's font provider. diff --git a/play-services-core/src/main/kotlin/org/microg/gms/measurement/MeasurementService.kt b/play-services-core/src/main/kotlin/org/microg/gms/measurement/MeasurementService.kt new file mode 100644 index 00000000..7f0a0afe --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/measurement/MeasurementService.kt @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.measurement + +import android.os.Bundle +import android.os.Parcel +import android.util.Log +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.measurement.internal.* +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "MeasurementService" + +class MeasurementService : BaseService(TAG, GmsService.MEASUREMENT) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete(CommonStatusCodes.SUCCESS, MeasurementServiceImpl(), Bundle()) + } +} + +class MeasurementServiceImpl : IMeasurementService.Stub() { + override fun f1(event: EventParcel, app: AppMetadata) { + Log.d(TAG, "f1($event) for $app") + } + + override fun f2(attribute: UserAttributeParcel?, app: AppMetadata) { + Log.d(TAG, "f2($attribute) for $app") + } + + override fun f4(app: AppMetadata) { + Log.d(TAG, "f4() for $app") + } + + override fun f10(p0: Long, p1: String?, p2: String?, p3: String?) { + Log.d(TAG, "f10($p0, $p1, $p2, $p3)") + } + + override fun f11(app: AppMetadata): String? { + Log.d(TAG, "f11() for $app") + return null + } + + override fun f12(property: ConditionalUserPropertyParcel, app: AppMetadata) { + Log.d(TAG, "f12($property) for $app") + } + + override fun setDefaultEventParameters(params: Bundle, app: AppMetadata) { + Log.d(TAG, "setDefaultEventParameters($params) for $app") + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt index 045d8640..ec83c681 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt @@ -15,6 +15,7 @@ import com.google.android.gms.phenotype.internal.IPhenotypeCallbacks import com.google.android.gms.phenotype.internal.IPhenotypeService import org.microg.gms.BaseService import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues private const val TAG = "GmsPhenotypeSvc" @@ -26,7 +27,12 @@ class PhenotypeService : BaseService(TAG, GmsService.PHENOTYPE) { class PhenotypeServiceImpl : IPhenotypeService.Stub() { override fun register(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: ByteArray?) { - Log.d(TAG, "register($p1, $p2, ${p3?.contentToString()}, $p4)") + Log.d(TAG, "register($p1, $p2, $p3, $p4)") + callbacks.onRegister(Status.SUCCESS) + } + + override fun register2(callbacks: IPhenotypeCallbacks, p1: String?, p2: Int, p3: Array?, p4: IntArray?, p5: ByteArray?) { + Log.d(TAG, "register2($p1, $p2, $p3, $p4, $p5)") callbacks.onRegister(Status.SUCCESS) } @@ -37,9 +43,5 @@ class PhenotypeServiceImpl : IPhenotypeService.Stub() { }) } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { - if (super.onTransact(code, data, reply, flags)) return true - Log.d(TAG, "onTransact [unknown]: $code, $data, $flags") - return false - } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/provision/ProvisionService.kt b/play-services-core/src/main/kotlin/org/microg/gms/provision/ProvisionService.kt index 27230072..b7c7da70 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/provision/ProvisionService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/provision/ProvisionService.kt @@ -14,10 +14,10 @@ import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay import org.microg.gms.checkin.getCheckinServiceInfo import org.microg.gms.checkin.setCheckinServiceConfiguration +import org.microg.gms.droidguard.core.DroidGuardPreferences import org.microg.gms.gcm.getGcmServiceInfo import org.microg.gms.gcm.setGcmServiceConfiguration -import org.microg.gms.safetynet.getSafetyNetServiceInfo -import org.microg.gms.safetynet.setSafetyNetServiceConfiguration +import org.microg.gms.safetynet.SafetyNetPreferences class ProvisionService : LifecycleService() { private fun Bundle.getBooleanOrNull(key: String): Boolean? { @@ -34,7 +34,10 @@ class ProvisionService : LifecycleService() { intent?.extras?.getBooleanOrNull("checkin_enabled")?.let { setCheckinServiceConfiguration(this@ProvisionService, getCheckinServiceInfo(this@ProvisionService).configuration.copy(enabled = it)) } intent?.extras?.getBooleanOrNull("gcm_enabled")?.let { setGcmServiceConfiguration(this@ProvisionService, getGcmServiceInfo(this@ProvisionService).configuration.copy(enabled = it)) } - intent?.extras?.getBooleanOrNull("safetynet_enabled")?.let { setSafetyNetServiceConfiguration(this@ProvisionService, getSafetyNetServiceInfo(this@ProvisionService).configuration.copy(enabled = it)) } + intent?.extras?.getBooleanOrNull("safetynet_enabled")?.let { + SafetyNetPreferences.setEnabled(this@ProvisionService, it) + DroidGuardPreferences.setEnabled(this@ProvisionService, it) + } // What else? delay(2 * 1000) // Wait 2 seconds to give provisioning some extra time diff --git a/play-services-core/src/main/kotlin/org/microg/gms/udc/FacsCacheService.kt b/play-services-core/src/main/kotlin/org/microg/gms/udc/FacsCacheService.kt index c12b37bf..c48e0e81 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/udc/FacsCacheService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/udc/FacsCacheService.kt @@ -5,6 +5,7 @@ package org.microg.gms.udc +import android.os.Parcel import android.util.Log import com.google.android.gms.common.api.Status import com.google.android.gms.common.internal.GetServiceRequest @@ -14,6 +15,7 @@ import com.google.android.gms.facs.cache.internal.IFacsCacheCallbacks import com.google.android.gms.facs.cache.internal.IFacsCacheService import org.microg.gms.BaseService import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues private const val TAG = "GmsFacsCache" @@ -49,4 +51,5 @@ class FacsCacheServiceImpl : IFacsCacheService.Stub() { callbacks.onWriteDeviceLevelSettingsResult(Status.CANCELED) } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } } diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index cdeb2007..196cee33 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -11,10 +11,12 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.Preference import com.google.android.gms.R -import org.microg.gms.checkin.getCheckinServiceInfo +import org.microg.gms.checkin.CheckinPrefs import org.microg.gms.gcm.GcmDatabase import org.microg.gms.gcm.getGcmServiceInfo -import org.microg.gms.safetynet.getSafetyNetServiceInfo +import org.microg.gms.safetynet.SafetyNetPreferences +import org.microg.nlp.client.GeocodeClient +import org.microg.nlp.client.LocationClient import org.microg.nlp.client.UnifiedLocationClient import org.microg.tools.ui.ResourceSettingsFragment @@ -68,10 +70,14 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_GCM)!!.setSummary(R.string.service_status_disabled_short) } - findPreference(PREF_CHECKIN)!!.setSummary(if (getCheckinServiceInfo(context).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - findPreference(PREF_SNET)!!.setSummary(if (getSafetyNetServiceInfo(context).configuration.enabled) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_CHECKIN)!!.setSummary(if (CheckinPrefs.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) + findPreference(PREF_SNET)!!.setSummary(if (SafetyNetPreferences.isEnabled(context)) R.string.service_status_enabled_short else R.string.service_status_disabled_short) - val backendCount = UnifiedLocationClient[context].getLocationBackends().size + UnifiedLocationClient[context].getGeocoderBackends().size + val backendCount = try { + LocationClient(context, lifecycle).getLocationBackends().size + GeocodeClient(context, lifecycle).getGeocodeBackends().size + } catch (e: Exception) { + 0 + } findPreference(PREF_UNIFIEDNLP)!!.summary = context.resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount) findPreference(PREF_EXPOSURE)?.isVisible = NearbyPreferencesIntegration.isAvailable diff --git a/play-services-core/src/main/kotlin/org/microg/gms/usagereporting/UsageReportingService.kt b/play-services-core/src/main/kotlin/org/microg/gms/usagereporting/UsageReportingService.kt new file mode 100644 index 00000000..86831b2e --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/usagereporting/UsageReportingService.kt @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.usagereporting + +import android.os.Bundle +import android.os.Parcel +import android.util.Log +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.usagereporting.UsageReportingOptInOptions +import com.google.android.gms.usagereporting.internal.IUsageReportingCallbacks +import com.google.android.gms.usagereporting.internal.IUsageReportingOptInOptionsChangedListener +import com.google.android.gms.usagereporting.internal.IUsageReportingService +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.utils.warnOnTransactionIssues + +private const val TAG = "UsageReportingService" + +class UsageReportingService : BaseService(TAG, GmsService.USAGE_REPORTING) { + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + callback.onPostInitComplete(CommonStatusCodes.SUCCESS, UsageReportingServiceImpl(), Bundle()) + } +} + +class UsageReportingServiceImpl : IUsageReportingService.Stub() { + override fun getOptInOptions(callbacks: IUsageReportingCallbacks) { + // Don't log to prevent log spam + callbacks.onOptInOptions(Status.SUCCESS, UsageReportingOptInOptions().apply { optInUsageReporting = 2 }) + } + + override fun setOptInOptions(options: UsageReportingOptInOptions, callbacks: IUsageReportingCallbacks) { + Log.d(TAG, "setOptInOptions($options)") + callbacks.onOptInOptionsSet(Status.SUCCESS) + } + + override fun addOptInOptionsChangedListener(listener: IUsageReportingOptInOptionsChangedListener, callbacks: IUsageReportingCallbacks) { + Log.d(TAG, "addOptInOptionsChangedListener($listener)") + callbacks.onOptInOptionsChangedListenerAdded(Status.SUCCESS) + } + + override fun removeOptInOptionsChangedListener(listener: IUsageReportingOptInOptionsChangedListener, callbacks: IUsageReportingCallbacks) { + Log.d(TAG, "removeOptInOptionsChangedListener($listener)") + callbacks.onOptInOptionsChangedListenerRemoved(Status.SUCCESS) + } + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } +} From 711e5c7e1217f08ad7e7e568c1b6d5088e255f82 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:56:02 +0100 Subject: [PATCH 46/63] Fix build of maps-vtm --- .../vtm-microg-theme/build.gradle | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/play-services-maps-core-vtm/vtm-microg-theme/build.gradle b/play-services-maps-core-vtm/vtm-microg-theme/build.gradle index 3304c9a0..8fcb9bba 100644 --- a/play-services-maps-core-vtm/vtm-microg-theme/build.gradle +++ b/play-services-maps-core-vtm/vtm-microg-theme/build.gradle @@ -1,11 +1,16 @@ apply plugin: 'java' -apply plugin: 'maven' dependencies { - compile "org.microg:vtm:0.9.1-mod" + implementation "org.microg:vtm:0.9.1-mod" } sourceSets { - main.java.srcDirs = ['src'] - main.resources.srcDirs = ['resources'] + main { + java { + srcDir 'src' + } + resources { + srcDir 'resources' + } + } } From 9f3a5ce32939962e9ae8bb6213204e3c1e5d1ec8 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 18 Jan 2022 18:57:30 +0100 Subject: [PATCH 47/63] Mapbox: Don't crash for invalid Polygons --- .../microg/gms/maps/mapbox/model/Polygon.kt | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt index e3d0d5ea..4177f286 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt @@ -1,17 +1,6 @@ /* - * Copyright (C) 2019 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. + * SPDX-FileCopyrightText: 2019 microG Project Team + * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.maps.mapbox.model @@ -80,7 +69,11 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option } strokes.forEachIndexed { idx, it -> if (idx > 0) it.points = this.holes[idx - 1] } if (this.holes.size + 1 > strokes.size) { - strokes.addAll(this.holes.subList(strokes.size, this.holes.size - 1).mapIndexed { idx, it -> PolylineImpl(map, "$id-stroke-hole-${strokes.size + idx}", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it)) }) + try { + strokes.addAll(this.holes.subList(strokes.size, this.holes.size - 1).mapIndexed { idx, it -> PolylineImpl(map, "$id-stroke-hole-${strokes.size + idx}", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it)) }) + } catch (e: Exception) { + Log.w(TAG, e) + } } map.fillManager?.let { update(it) } } @@ -164,4 +157,4 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option companion object { private val TAG = "GmsMapPolygon" } -} \ No newline at end of file +} From 3bbae67fda6b4c2d8fad0b8a2d5834966d33183f Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 22 Jan 2022 19:18:45 +0100 Subject: [PATCH 48/63] Claim to provide tapandpay_token_listing feature --- .../kotlin/org/microg/gms/tapandpay/TapAndPayService.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 0a08696b..0c894b48 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 @@ -7,8 +7,10 @@ package org.microg.gms.tapandpay import android.os.Parcel import android.os.RemoteException import android.util.Log +import com.google.android.gms.common.Feature import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.common.api.Status +import com.google.android.gms.common.internal.ConnectionInfo import com.google.android.gms.common.internal.GetServiceRequest import com.google.android.gms.common.internal.IGmsCallbacks import com.google.android.gms.tapandpay.TapAndPayStatusCodes.TAP_AND_PAY_NO_ACTIVE_WALLET @@ -21,7 +23,11 @@ private const val TAG = "GmsTapAndPay" class TapAndPayService : BaseService(TAG, GmsService.TAP_AND_PAY) { override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { - callback.onPostInitComplete(CommonStatusCodes.SUCCESS, TapAndPayImpl(), null) + callback.onPostInitCompleteWithConnectionInfo(CommonStatusCodes.SUCCESS, TapAndPayImpl(), ConnectionInfo().apply { + features = arrayOf( + Feature("tapandpay_token_listing", 3) + ) + }) } } From 6e21b52bfe0c841514a7b13fd1467d0fd07f7c86 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 22 Jan 2022 21:44:05 +0100 Subject: [PATCH 49/63] Profile Manager: Add configuration features --- .../org/microg/gms/profile/ProfileManager.kt | 149 ++++++++++++++---- .../microg/gms/utils/FileXmlResourceParser.kt | 127 +++++++++++++++ .../DeviceRegistrationPreferencesFragment.kt | 99 +++++++++++- .../xml/preferences_device_registration.xml | 8 +- .../src/main/res/xml/profile_bullhead_27.xml | 5 +- .../res/xml/profile_lineage_falcon_25.xml | 36 +++++ 6 files changed, 389 insertions(+), 35 deletions(-) create mode 100644 play-services-base-core/src/main/kotlin/org/microg/gms/utils/FileXmlResourceParser.kt create mode 100644 play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml 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 89251a9b..f14f14ad 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 @@ -7,10 +7,13 @@ package org.microg.gms.profile import android.annotation.SuppressLint import android.content.Context +import android.content.res.XmlResourceParser import android.util.Log import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract.Profile +import org.microg.gms.utils.FileXmlResourceParser import org.xmlpull.v1.XmlPullParser +import java.io.File import java.util.* import kotlin.random.Random @@ -19,18 +22,67 @@ object ProfileManager { const val PROFILE_REAL = "real" const val PROFILE_AUTO = "auto" const val PROFILE_NATIVE = "native" + const val PROFILE_USER = "user" + const val PROFILE_SYSTEM = "system" - private var initialized = false + private var activeProfile: String? = null - private fun getProfileFromSettings(context: Context) = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } - private fun getAutoProfile(context: Context): String { + private fun getUserProfileFile(context: Context): File = File(context.filesDir, "device_profile.xml") + private fun getSystemProfileFile(context: Context): File = File("/system/etc/microg_device_profile.xml") + private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) + + fun getConfiguredProfile(context: Context): String = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } ?: PROFILE_AUTO + + fun getAutoProfile(context: Context): String { + if (hasProfile(context, PROFILE_SYSTEM) && isAutoProfile(context, PROFILE_SYSTEM)) return PROFILE_SYSTEM val profile = "${android.os.Build.PRODUCT}_${android.os.Build.VERSION.SDK_INT}" - if (hasProfile(context, profile)) return profile + if (hasProfile(context, profile) && isAutoProfile(context, profile)) return profile return PROFILE_NATIVE } - private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) - private fun hasProfile(context: Context, profile: String): Boolean = getProfileResId(context, profile) != 0 + fun hasProfile(context: Context, profile: String): Boolean = when (profile) { + PROFILE_AUTO -> hasProfile(context, getAutoProfile(context)) + PROFILE_NATIVE, PROFILE_REAL -> true + PROFILE_USER -> getUserProfileFile(context).exists() + PROFILE_SYSTEM -> getSystemProfileFile(context).exists() + else -> getProfileResId(context, profile) != 0 + } + + private fun getProfileXml(context: Context, profile: String): XmlResourceParser? = kotlin.runCatching { + when (profile) { + PROFILE_AUTO -> getProfileXml(context, getAutoProfile(context)) + PROFILE_NATIVE, PROFILE_REAL -> null + PROFILE_USER -> FileXmlResourceParser(getUserProfileFile(context)) + PROFILE_SYSTEM -> FileXmlResourceParser(getSystemProfileFile(context)) + else -> { + val profileResId = getProfileResId(context, profile) + if (profileResId == 0) return@runCatching null + context.resources.getXml(profileResId) + } + } + }.getOrNull() + + fun isAutoProfile(context: Context, profile: String): Boolean = kotlin.runCatching { + when (profile) { + PROFILE_AUTO -> false + PROFILE_REAL -> false + PROFILE_NATIVE -> true + else -> getProfileXml(context, profile)?.use { + var next = it.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (it.name) { + "profile" -> { + return@use it.getAttributeBooleanValue(null, "auto", false) + } + } + } + next = it.next() + } + } == true + } + }.getOrDefault(false) + private fun getProfileData(context: Context, profile: String, realData: Map): Map { try { if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData @@ -38,7 +90,7 @@ object ProfileManager { if (profileResId == 0) return realData val resultData = mutableMapOf() resultData.putAll(realData) - context.resources.getXml(profileResId).use { + getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { @@ -61,7 +113,7 @@ object ProfileManager { } } - private fun getActiveProfile(context: Context) = getProfileFromSettings(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } + private fun getProfile(context: Context) = getConfiguredProfile(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } private fun getSerialFromSettings(context: Context): String? = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.SERIAL)) { it.getString(0) } private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) } @@ -99,18 +151,15 @@ object ProfileManager { // From profile try { - val profileResId = getProfileResId(context, profile) - if (profileResId != 0) { - context.resources.getXml(profileResId).use { - var next = it.next() - while (next != XmlPullParser.END_DOCUMENT) { - when (next) { - XmlPullParser.START_TAG -> when (it.name) { - "serial" -> return it.getAttributeValue(null, "template") - } + getProfileXml(context, profile)?.use { + var next = it.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (it.name) { + "serial" -> return it.getAttributeValue(null, "template") } - next = it.next() } + next = it.next() } } } catch (e: Exception) { @@ -118,18 +167,18 @@ object ProfileManager { } // Fallback - return "008741A0B2C4D6E8" + return randomSerial("008741A0B2C4D6E8") } @SuppressLint("MissingPermission") - private fun getEffectiveProfileSerial(context: Context, profile: String): String { - getSerialFromSettings(context)?.let { return it } + fun getSerial(context: Context, profile: String = getProfile(context), local: Boolean = false): String { + if (!local) getSerialFromSettings(context)?.let { return it } val serialTemplate = getProfileSerialTemplate(context, profile) val serial = when { profile == PROFILE_REAL && serialTemplate != android.os.Build.UNKNOWN -> serialTemplate else -> randomSerial(serialTemplate) } - saveSerial(context, serial) + if (!local) saveSerial(context, serial) return serial } @@ -210,29 +259,67 @@ object ProfileManager { } } - private fun applyProfile(context: Context, profile: String) { - val profileData = getProfileData(context, profile, getRealData()) ?: getRealData() + private fun applyProfile(context: Context, profile: String, serial: String = getSerial(context, profile)) { + val profileData = getProfileData(context, profile, getRealData()) + if (Log.isLoggable(TAG, Log.VERBOSE)) { + for ((key, value) in profileData) { + Log.v(TAG, "") + } + } applyProfileData(profileData) - Build.SERIAL = getEffectiveProfileSerial(context, profile) + Build.SERIAL = serial Log.d(TAG, "Using Serial ${Build.SERIAL}") + activeProfile = profile + } + + fun getProfileName(context: Context, profile: String): String? = getProfileName { getProfileXml(context, profile) } + + private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? = parserCreator()?.use { + var next = it.next() + while (next != XmlPullParser.END_DOCUMENT) { + when (next) { + XmlPullParser.START_TAG -> when (it.name) { + "profile" -> { + return@use it.getAttributeValue(null, "name") + } + } + } + next = it.next() + } + null } fun setProfile(context: Context, profile: String?) { + val changed = getProfile(context) != profile + val newProfile = profile ?: PROFILE_AUTO + val newSerial = if (changed) getSerial(context, newProfile, true) else getSerial(context) SettingsContract.setSettings(context, Profile.getContentUri(context)) { - put(Profile.PROFILE, profile) - put(Profile.SERIAL, null as String?) + put(Profile.PROFILE, newProfile) + if (changed) put(Profile.SERIAL, newSerial) + } + if (changed && activeProfile != null) applyProfile(context, newProfile, newSerial) + } + + fun importUserProfile(context: Context, file: File): Boolean { + val profileName = getProfileName { FileXmlResourceParser(file) } ?: return false + try { + Log.d(TAG, "Importing user profile '$profileName'") + file.copyTo(getUserProfileFile(context)) + if (activeProfile == PROFILE_USER) applyProfile(context, PROFILE_USER) + return true + } catch (e: Exception) { + Log.w(TAG, e) + return false } - applyProfile(context, profile ?: PROFILE_AUTO) } @JvmStatic fun ensureInitialized(context: Context) { synchronized(this) { - if (initialized) return try { - val profile = getActiveProfile(context) + val profile = getProfile(context) + if (activeProfile == profile) return applyProfile(context, profile) - initialized = true } catch (e: Exception) { Log.w(TAG, e) } diff --git a/play-services-base-core/src/main/kotlin/org/microg/gms/utils/FileXmlResourceParser.kt b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/FileXmlResourceParser.kt new file mode 100644 index 00000000..689d0f46 --- /dev/null +++ b/play-services-base-core/src/main/kotlin/org/microg/gms/utils/FileXmlResourceParser.kt @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2022 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.utils + +import android.content.res.XmlResourceParser +import android.util.Xml +import org.xmlpull.v1.XmlPullParser +import java.io.Closeable +import java.io.File +import java.io.FileReader +import java.io.Reader + +class FileXmlResourceParser(private val reader: Reader, private val parser: XmlPullParser = Xml.newPullParser()) : + XmlResourceParser, + XmlPullParser by parser, + Closeable by reader { + constructor(file: File) : this(FileReader(file)) + + init { + parser.setInput(reader) + } + + override fun getAttributeNameResource(index: Int): Int { + return 0 + } + + override fun getAttributeListValue( + namespace: String?, attribute: String?, + options: Array?, defaultValue: Int + ): Int { + val s = getAttributeValue(namespace, attribute) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeBooleanValue( + namespace: String?, attribute: String?, + defaultValue: Boolean + ): Boolean { + + val s = getAttributeValue(namespace, attribute) + return s?.toBooleanStrictOrNull() ?: defaultValue + } + + override fun getAttributeResourceValue( + namespace: String?, attribute: String?, + defaultValue: Int + ): Int { + val s = getAttributeValue(namespace, attribute) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeIntValue( + namespace: String?, attribute: String?, + defaultValue: Int + ): Int { + val s = getAttributeValue(namespace, attribute) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeUnsignedIntValue( + namespace: String?, attribute: String?, + defaultValue: Int + ): Int { + val s = getAttributeValue(namespace, attribute) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeFloatValue( + namespace: String?, attribute: String?, + defaultValue: Float + ): Float { + val s = getAttributeValue(namespace, attribute) + return s?.toFloat() ?: defaultValue + } + + override fun getAttributeListValue( + index: Int, + options: Array?, defaultValue: Int + ): Int { + val s = getAttributeValue(index) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeBooleanValue(index: Int, defaultValue: Boolean): Boolean { + val s = getAttributeValue(index) + return s?.toBooleanStrictOrNull() ?: defaultValue + } + + override fun getAttributeResourceValue(index: Int, defaultValue: Int): Int { + val s = getAttributeValue(index) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeIntValue(index: Int, defaultValue: Int): Int { + val s = getAttributeValue(index) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeUnsignedIntValue(index: Int, defaultValue: Int): Int { + val s = getAttributeValue(index) + return s?.toInt() ?: defaultValue + } + + override fun getAttributeFloatValue(index: Int, defaultValue: Float): Float { + val s = getAttributeValue(index) + return s?.toFloat() ?: defaultValue + } + + override fun getIdAttribute(): String? { + return getAttributeValue(null, "id") + } + + override fun getClassAttribute(): String? { + return getAttributeValue(null, "class") + } + + override fun getIdAttributeResourceValue(defaultValue: Int): Int { + return getAttributeResourceValue(null, "id", defaultValue) + } + + override fun getStyleAttribute(): Int { + return getAttributeResourceValue(null, "style", 0) + } +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt index 6d27eed6..ced09539 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt @@ -5,31 +5,122 @@ package org.microg.gms.ui +import android.net.Uri import android.os.Bundle import android.os.Handler import android.text.format.DateUtils +import android.util.Log +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope +import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import org.microg.gms.checkin.getCheckinServiceInfo +import org.microg.gms.profile.ProfileManager +import org.microg.gms.profile.ProfileManager.PROFILE_AUTO +import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE +import org.microg.gms.profile.ProfileManager.PROFILE_REAL +import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM +import org.microg.gms.profile.ProfileManager.PROFILE_USER +import java.io.File +import java.io.FileOutputStream class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { + private lateinit var deviceProfile: ListPreference + private lateinit var importProfile: Preference + private lateinit var serial: Preference private lateinit var statusCategory: PreferenceCategory private lateinit var status: Preference private lateinit var androidId: Preference private val handler = Handler() private val updateRunnable = Runnable { updateStatus() } + private lateinit var profileFileImport: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) + } + + private fun onFileSelected(uri: Uri?) { + if (uri == null) return + try { + val context = requireContext() + val file = File.createTempFile("profile_", ".xml", context.cacheDir) + context.contentResolver.openInputStream(uri)?.use { inputStream -> + FileOutputStream(file).use { inputStream.copyTo(it) } + } + val success = ProfileManager.importUserProfile(context, file) + file.delete() + if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { + ProfileManager.setProfile(context, PROFILE_USER) + } + updateStatus() + } catch (e: Exception) { + Log.w(TAG, e) + } + } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_device_registration) } override fun onBindPreferences() { + deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile + importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile + serial = preferenceScreen.findPreference("pref_device_serial") ?: serial statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory status = preferenceScreen.findPreference("pref_device_registration_status") ?: status androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId + + deviceProfile.setOnPreferenceChangeListener { _, newValue -> + ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) + updateStatus() + true + } + importProfile.setOnPreferenceClickListener { + profileFileImport.launch("text/xml") + true + } + } + + private fun configureProfilePreference() { + val context = requireContext() + val configuredProfile = ProfileManager.getConfiguredProfile(context) + val autoProfile = ProfileManager.getAutoProfile(context) + val autoProfileName = when (autoProfile) { + PROFILE_NATIVE -> "Native" + PROFILE_REAL -> "Real" + else -> ProfileManager.getProfileName(context, autoProfile) + } + val profiles = + mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) + val profileNames = mutableListOf("Automatic: $autoProfileName", "Native", "Real") + if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { + profiles.add(PROFILE_SYSTEM) + profileNames.add("System: ${ProfileManager.getProfileName(context, PROFILE_SYSTEM)}") + } + if (ProfileManager.hasProfile(context, PROFILE_USER)) { + profiles.add(PROFILE_USER) + profileNames.add("Custom: ${ProfileManager.getProfileName(context, PROFILE_USER)}") + } + for (profile in R.xml::class.java.declaredFields.map { it.name } + .filter { it.startsWith("profile_") } + .map { it.substring(8) } + .sorted()) { + val profileName = ProfileManager.getProfileName(context, profile) + if (profileName != null) { + profiles.add(profile) + profileNames.add(profileName) + } + } + deviceProfile.entryValues = profiles.toTypedArray() + deviceProfile.entries = profileNames.toTypedArray() + deviceProfile.value = configuredProfile + deviceProfile.summary = + profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" } override fun onResume() { @@ -43,13 +134,19 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { } private fun updateStatus() { + handler.removeCallbacks(updateRunnable) handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { + configureProfilePreference() + serial.summary = ProfileManager.getSerial(appContext) val serviceInfo = getCheckinServiceInfo(appContext) statusCategory.isVisible = serviceInfo.configuration.enabled if (serviceInfo.lastCheckin > 0) { - status.summary = getString(R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0)) + status.summary = getString( + R.string.checkin_last_registration, + DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) + ) androidId.isVisible = true androidId.summary = serviceInfo.androidId.toString(16) } else { diff --git a/play-services-core/src/main/res/xml/preferences_device_registration.xml b/play-services-core/src/main/res/xml/preferences_device_registration.xml index 6c82e63c..4ad1fae0 100644 --- a/play-services-core/src/main/res/xml/preferences_device_registration.xml +++ b/play-services-core/src/main/res/xml/preferences_device_registration.xml @@ -11,12 +11,18 @@ android:title="Device profile"> + android:title="Import custom profile" /> + - + @@ -27,6 +27,7 @@ + diff --git a/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml b/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml new file mode 100644 index 00000000..57beb5d4 --- /dev/null +++ b/play-services-core/src/main/res/xml/profile_lineage_falcon_25.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 91071bbea12596741f222430d2ffbae4a7461ac1 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 24 Jan 2022 19:11:36 +0100 Subject: [PATCH 50/63] Move ReCAPTCHA Activity to UI package --- build.gradle | 2 +- play-services-core/build.gradle | 1 + play-services-safetynet-core-ui/build.gradle | 59 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 25 ++++++++ .../microg/gms/safetynet/ReCaptchaActivity.kt | 4 +- .../src/main/res/drawable/ic_recaptcha.xml | 0 .../src/main/res/layout/recaptcha_window.xml | 0 play-services-safetynet-core/build.gradle | 2 - .../src/main/AndroidManifest.xml | 7 --- .../gms/safetynet/SafetyNetClientService.kt | 8 ++- settings.gradle | 1 + 11 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 play-services-safetynet-core-ui/build.gradle create mode 100644 play-services-safetynet-core-ui/src/main/AndroidManifest.xml rename {play-services-safetynet-core => play-services-safetynet-core-ui}/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt (98%) rename {play-services-safetynet-core => play-services-safetynet-core-ui}/src/main/res/drawable/ic_recaptcha.xml (100%) rename {play-services-safetynet-core => play-services-safetynet-core-ui}/src/main/res/layout/recaptcha_window.xml (100%) diff --git a/build.gradle b/build.gradle index e873b2bb..1652eaec 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { ext.coroutineVersion = '1.5.2' ext.annotationVersion = '1.2.0' - ext.appcompatVersion = '1.4.0' + ext.appcompatVersion = '1.4.1' ext.coreVersion = '1.7.0' ext.fragmentVersion = '1.4.0' ext.lifecycleVersion = '2.4.0' diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 05d07ce5..285970ad 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -50,6 +50,7 @@ dependencies { withNearbyImplementation project(':play-services-nearby-core') withNearbyImplementation project(':play-services-nearby-core-ui') implementation project(':play-services-safetynet-core') + implementation project(':play-services-safetynet-core-ui') implementation project(':play-services-tapandpay-core') implementation project(':play-services-vision-core') diff --git a/play-services-safetynet-core-ui/build.gradle b/play-services-safetynet-core-ui/build.gradle new file mode 100644 index 00000000..23dd3f45 --- /dev/null +++ b/play-services-safetynet-core-ui/build.gradle @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2021 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' +apply plugin: 'signing' + +dependencies { + api project(':play-services-safetynet-api') + + implementation project(':play-services-base-core') + implementation project(':play-services-base-core-ui') + implementation project(':play-services-droidguard') + implementation project(':play-services-droidguard-core') + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" + + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation "androidx.core:core-ktx:$coreVersion" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" + implementation "androidx.webkit:webkit:$webkitVersion" +} + +android { + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'MissingTranslation' + } + + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'UI for microG service implementation for play-services-safetynet' diff --git a/play-services-safetynet-core-ui/src/main/AndroidManifest.xml b/play-services-safetynet-core-ui/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b12472f9 --- /dev/null +++ b/play-services-safetynet-core-ui/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt b/play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt similarity index 98% rename from play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt rename to play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt index 98f307f9..9808fcc5 100644 --- a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt +++ b/play-services-safetynet-core-ui/src/main/kotlin/org/microg/gms/safetynet/ReCaptchaActivity.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.lifecycleScope import androidx.webkit.WebViewClientCompat import com.google.android.gms.safetynet.SafetyNetStatusCodes.* import org.microg.gms.droidguard.core.DroidGuardResultCreator -import org.microg.gms.safetynet.core.R +import org.microg.gms.safetynet.core.ui.R import java.io.ByteArrayInputStream import java.net.URLEncoder import java.security.MessageDigest @@ -30,7 +30,7 @@ import kotlin.math.min private const val TAG = "GmsReCAPTCHA" -fun StringBuilder.appendUrlEncodedParam(key: String, value: String?) = append("&") +private fun StringBuilder.appendUrlEncodedParam(key: String, value: String?) = append("&") .append(URLEncoder.encode(key, "UTF-8")) .append("=") .append(value?.let { URLEncoder.encode(it, "UTF-8") } ?: "") diff --git a/play-services-safetynet-core/src/main/res/drawable/ic_recaptcha.xml b/play-services-safetynet-core-ui/src/main/res/drawable/ic_recaptcha.xml similarity index 100% rename from play-services-safetynet-core/src/main/res/drawable/ic_recaptcha.xml rename to play-services-safetynet-core-ui/src/main/res/drawable/ic_recaptcha.xml diff --git a/play-services-safetynet-core/src/main/res/layout/recaptcha_window.xml b/play-services-safetynet-core-ui/src/main/res/layout/recaptcha_window.xml similarity index 100% rename from play-services-safetynet-core/src/main/res/layout/recaptcha_window.xml rename to play-services-safetynet-core-ui/src/main/res/layout/recaptcha_window.xml diff --git a/play-services-safetynet-core/build.gradle b/play-services-safetynet-core/build.gradle index 7bd08af3..b96ab704 100644 --- a/play-services-safetynet-core/build.gradle +++ b/play-services-safetynet-core/build.gradle @@ -20,11 +20,9 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" - implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation "androidx.core:core-ktx:$coreVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" - implementation "androidx.webkit:webkit:$webkitVersion" implementation "com.android.volley:volley:$volleyVersion" implementation "com.squareup.wire:wire-runtime:$wireVersion" diff --git a/play-services-safetynet-core/src/main/AndroidManifest.xml b/play-services-safetynet-core/src/main/AndroidManifest.xml index 44348cdf..6941a154 100644 --- a/play-services-safetynet-core/src/main/AndroidManifest.xml +++ b/play-services-safetynet-core/src/main/AndroidManifest.xml @@ -13,12 +13,5 @@ - - diff --git a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt index 228d4ec8..4ba52f63 100644 --- a/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt +++ b/play-services-safetynet-core/src/main/kotlin/org/microg/gms/safetynet/SafetyNetClientService.kt @@ -35,6 +35,7 @@ import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract.CheckIn.getContentUri import org.microg.gms.settings.SettingsContract.getSettings import java.io.IOException +import java.net.URLEncoder import java.util.* private const val TAG = "GmsSafetyNet" @@ -46,6 +47,10 @@ class SafetyNetClientService : BaseService(TAG, GmsService.SAFETY_NET_CLIENT) { } } +private fun StringBuilder.appendUrlEncodedParam(key: String, value: String?) = append("&") + .append(URLEncoder.encode(key, "UTF-8")) + .append("=") + .append(value?.let { URLEncoder.encode(it, "UTF-8") } ?: "") class SafetyNetClientServiceImpl(private val context: Context, private val packageName: String, private val lifecycle: Lifecycle) : ISafetyNetService.Stub(), LifecycleOwner { override fun getLifecycle(): Lifecycle = lifecycle @@ -133,7 +138,8 @@ class SafetyNetClientServiceImpl(private val context: Context, private val packa return } - val intent = Intent(context, ReCaptchaActivity::class.java) + val intent = Intent("org.microg.gms.safetynet.RECAPTCHA_ACTIVITY") + intent.`package` = context.packageName intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) diff --git a/settings.gradle b/settings.gradle index f8812b76..1d066117 100644 --- a/settings.gradle +++ b/settings.gradle @@ -62,6 +62,7 @@ include ':play-services-vision-core' include ':play-services-base-core-ui' include ':play-services-droidguard-core-ui' include ':play-services-nearby-core-ui' +include ':play-services-safetynet-core-ui' include ':firebase-auth-core' From 7969aa817b3455708997dbbe6ce8b5f4015f1d31 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 24 Jan 2022 19:51:54 +0100 Subject: [PATCH 51/63] Fix Lint issues --- .../main/java/org/microg/gms/auth/AuthManagerServiceImpl.java | 2 ++ .../src/main/java/org/microg/gms/ui/PlacePickerActivity.java | 1 + 2 files changed, 3 insertions(+) diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index b2722270..d2f5b662 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -20,6 +20,7 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; +import android.annotation.SuppressLint; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; @@ -205,6 +206,7 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override + @SuppressLint("MissingPermission") // Workaround bug in Android Linter public Bundle clearToken(String token, Bundle extras) { String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME); if (packageName == null) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); diff --git a/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java index b84cb274..e9563348 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/PlacePickerActivity.java @@ -146,6 +146,7 @@ PlacePickerActivity extends AppCompatActivity /*implements Map.UpdateListener*/ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 0) { for (int grantResult : grantResults) { if (grantResult != PERMISSION_GRANTED) return; From 4a5c98491bcfe4754b3efcfed20f3ada75a6ebec Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 24 Jan 2022 19:53:07 +0100 Subject: [PATCH 52/63] Bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1652eaec..940da53a 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ def execResult(...args) { return stdout.toString().trim() } -def gmsVersion = "21.45.16" +def gmsVersion = "21.48.16" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) def gitVersionBase = execResult('git', 'describe', '--tags', '--abbrev=0', '--match=v[0-9]*').substring(1) def gitCommitCount = Integer.parseInt(execResult('git', 'rev-list', '--count', "v$gitVersionBase..HEAD")) From 6cfc0aa255bc0821b72f13b38ff7c5c0ab7fbf6e Mon Sep 17 00:00:00 2001 From: Marcus Hoffmann Date: Mon, 8 Nov 2021 18:28:27 +0100 Subject: [PATCH 53/63] request BLUETOOTH_SCAN and ADVERTISE permissions on Android 12 This also adds a warning notification when the app doesn't have the required permission after an OS update. --- .../ExposureNotificationsConfirmActivity.kt | 6 +++- ...xposureNotificationsPreferencesFragment.kt | 29 +++++++++++++++++-- .../src/main/res/values/strings.xml | 2 ++ .../preferences_exposure_notifications.xml | 8 +++++ .../exposurenotification/AdvertiserService.kt | 18 ++++++++++-- .../nearby/exposurenotification/Constants.kt | 2 ++ .../exposurenotification/NotifyService.kt | 9 +++++- .../exposurenotification/ScannerService.kt | 20 ++++++++----- .../src/main/res/values/strings.xml | 1 + 9 files changed, 80 insertions(+), 15 deletions(-) diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt index dc106094..225a0e02 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt @@ -105,7 +105,11 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() { private var permissionNeedsHandling: Boolean = false private var permissionRequestCode = 33 private val permissions by lazy { - if (Build.VERSION.SDK_INT >= 29) { + if (Build.VERSION.SDK_INT >= 31){ + arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN", "android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION") + + } + else if (Build.VERSION.SDK_INT >= 29) { arrayOf("android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION") } else { arrayOf("android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION") diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt index ec89883c..5003182d 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt @@ -8,12 +8,13 @@ package org.microg.gms.nearby.core.ui import android.bluetooth.BluetoothAdapter import android.content.Context.LOCATION_SERVICE import android.content.Intent +import android.content.pm.PackageManager import android.location.LocationManager +import android.os.Build import android.os.Bundle import android.os.Handler import android.provider.Settings -import android.util.Log -import android.view.View +import androidx.core.content.ContextCompat import androidx.core.location.LocationManagerCompat import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope @@ -31,6 +32,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { private lateinit var exposureEnableInfo: Preference private lateinit var exposureBluetoothOff: Preference private lateinit var exposureLocationOff: Preference + private lateinit var exposureNearbyNotGranted: Preference private lateinit var exposureBluetoothUnsupported: Preference private lateinit var exposureBluetoothNoAdvertisement: Preference private lateinit var exposureApps: PreferenceCategory @@ -41,6 +43,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { private val handler = Handler() private val updateStatusRunnable = Runnable { updateStatus() } private val updateContentRunnable = Runnable { updateContent() } + private var permissionRequestCode = 33 override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_exposure_notifications) @@ -50,6 +53,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { exposureEnableInfo = preferenceScreen.findPreference("pref_exposure_enable_info") ?: exposureEnableInfo exposureBluetoothOff = preferenceScreen.findPreference("pref_exposure_error_bluetooth_off") ?: exposureBluetoothOff exposureLocationOff = preferenceScreen.findPreference("pref_exposure_error_location_off") ?: exposureLocationOff + exposureNearbyNotGranted = preferenceScreen.findPreference("pref_exposure_error_nearby_not_granted") ?: exposureNearbyNotGranted exposureBluetoothUnsupported = preferenceScreen.findPreference("pref_exposure_error_bluetooth_unsupported") ?: exposureBluetoothUnsupported exposureBluetoothNoAdvertisement = preferenceScreen.findPreference("pref_exposure_error_bluetooth_no_advertise") ?: exposureBluetoothNoAdvertisement exposureApps = preferenceScreen.findPreference("prefcat_exposure_apps") ?: exposureApps @@ -80,12 +84,28 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { true } + exposureNearbyNotGranted.onPreferenceClickListener = Preference.OnPreferenceClickListener { + val nearbyPermissions = arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN") + requestPermissions(nearbyPermissions, ++permissionRequestCode) + true + } + collectedRpis.onPreferenceClickListener = Preference.OnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openExposureRpis) true } } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == this.permissionRequestCode) { + updateStatus() + // Tell the NotifyService that it should update the notification + val intent = Intent(NOTIFICATION_UPDATE_ACTION) + requireContext().sendBroadcast(intent) + } + } + override fun onResume() { super.onResume() @@ -110,6 +130,11 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() { val bluetoothSupported = ScannerService.isSupported(appContext) val advertisingSupported = if (bluetoothSupported == true) AdvertiserService.isSupported(appContext) else bluetoothSupported + val nearbyPermissions = arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN") + val nearbyPermissionsGranted = Build.VERSION.SDK_INT >= 31 || nearbyPermissions.all { + ContextCompat.checkSelfPermission(appContext, it) == PackageManager.PERMISSION_GRANTED + } + exposureNearbyNotGranted.isVisible = enabled && !nearbyPermissionsGranted exposureLocationOff.isVisible = enabled && bluetoothSupported != false && !LocationManagerCompat.isLocationEnabled(appContext.getSystemService(LOCATION_SERVICE) as LocationManager) exposureBluetoothOff.isVisible = enabled && bluetoothSupported == null && !turningBluetoothOn exposureBluetoothUnsupported.isVisible = enabled && bluetoothSupported == false diff --git a/play-services-nearby-core-ui/src/main/res/values/strings.xml b/play-services-nearby-core-ui/src/main/res/values/strings.xml index ddf2dfcc..7aaa43e9 100644 --- a/play-services-nearby-core-ui/src/main/res/values/strings.xml +++ b/play-services-nearby-core-ui/src/main/res/values/strings.xml @@ -76,4 +76,6 @@ Your identity or test result won't be shared with other people." Bluetooth needs to be enabled. Location access is required. Enable + New Permissions required + Tap to grant required permissions to Exposure Notifications diff --git a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml index 6c04ef7c..8981daa7 100644 --- a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml +++ b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml @@ -31,6 +31,14 @@ app:isPreferenceVisible="false" tools:isPreferenceVisible="true" /> + + = 26) { wantStartAdvertising = true - advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback) + try { + advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback) + } catch (e: SecurityException) { + Log.i(TAG, "Tried calling stopAdvertisingSet without android.permission.BLUETOOTH_ADVERTISE permission.", ) + } } else { advertiser?.stopAdvertising(callback) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt index ca695aac..2f429384 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt @@ -42,3 +42,5 @@ const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000L const val VERSION_1_0: Byte = 0x40 const val VERSION_1_1: Byte = 0x50 + +const val NOTIFICATION_UPDATE_ACTION = "org.microg.gms.nearby.UPDATE_NOTIFICATION" diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt index 48682281..0245752a 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt @@ -12,6 +12,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager import android.graphics.Color import android.location.LocationManager import android.os.Build @@ -53,9 +54,14 @@ class NotifyService : LifecycleService() { private fun updateNotification() { val location = !LocationManagerCompat.isLocationEnabled(getSystemService(Context.LOCATION_SERVICE) as LocationManager) val bluetooth = BluetoothAdapter.getDefaultAdapter()?.state.let { it != BluetoothAdapter.STATE_ON && it != BluetoothAdapter.STATE_TURNING_ON } - Log.d(TAG, "notify: location: $location, bluetooth: $bluetooth") + val nearbyPermissions = arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN") + val permissionNeedsHandling = Build.VERSION.SDK_INT >= 31 && nearbyPermissions.any { + ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED + } + Log.d( TAG,"notify: location: $location, bluetooth: $bluetooth, permissionNeedsHandling: $permissionNeedsHandling") val text: String = when { + permissionNeedsHandling -> getString(R.string.exposure_notify_off_nearby) location && bluetooth -> getString(R.string.exposure_notify_off_bluetooth_location) location -> getString(R.string.exposure_notify_off_location) bluetooth -> getString(R.string.exposure_notify_off_bluetooth) @@ -105,6 +111,7 @@ class NotifyService : LifecycleService() { addAction(BluetoothAdapter.ACTION_STATE_CHANGED) if (Build.VERSION.SDK_INT >= 19) addAction(LocationManager.MODE_CHANGED_ACTION) addAction(LocationManager.PROVIDERS_CHANGED_ACTION) + addAction(NOTIFICATION_UPDATE_ACTION) }) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index addc6c8c..9ee699e0 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -121,14 +121,18 @@ class ScannerService : LifecycleService() { Log.i(TAG, "Starting scanner for service $SERVICE_UUID for ${SCANNING_TIME_MS}ms") seenAdvertisements = 0 wakeLock.acquire() - scanner.startScan( - listOf(ScanFilter.Builder() - .setServiceUuid(SERVICE_UUID) - .setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0)) - .build()), - ScanSettings.Builder().build(), - callback - ) + try { + scanner.startScan( + listOf(ScanFilter.Builder() + .setServiceUuid(SERVICE_UUID) + .setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0)) + .build()), + ScanSettings.Builder().build(), + callback + ) + } catch (e: SecurityException) { + Log.e(TAG, "Couldn't start ScannerService, need android.permission.BLUETOOTH_SCAN permission.") + } scanning = true lastStartTime = System.currentTimeMillis() handler.postDelayed(stopLaterRunnable, SCANNING_TIME_MS) diff --git a/play-services-nearby-core/src/main/res/values/strings.xml b/play-services-nearby-core/src/main/res/values/strings.xml index 8cd79f35..16c274d9 100644 --- a/play-services-nearby-core/src/main/res/values/strings.xml +++ b/play-services-nearby-core/src/main/res/values/strings.xml @@ -7,4 +7,5 @@ Bluetooth needs to be enabled to receive Exposure Notifications. Location access is required to receive Exposure Notifications. Bluetooth and Location access need to be enabled to receive Exposure Notifications. + Exposure Notifications require additional permissions to work From 80b31293565c1ae5397ad31776cfec8c4354e510 Mon Sep 17 00:00:00 2001 From: Marcus Hoffmann Date: Tue, 9 Nov 2021 03:08:01 +0100 Subject: [PATCH 54/63] improve background location request flow Instead of directly jumping to the settings screen (which is what the "Request background location access" amounts to) and leaving the user there without a clue what to do, we update the label, icon and button with new text explaining what the user needs to do next. --- .../ExposureNotificationsConfirmActivity.kt | 92 +++++++++++++------ .../res/drawable/ic_outline_location_on.xml | 13 +++ ...xposure_notifications_confirm_activity.xml | 48 ++++++++++ .../src/main/res/values-de/strings.xml | 4 + .../src/main/res/values/strings.xml | 2 + 5 files changed, 133 insertions(+), 26 deletions(-) create mode 100644 play-services-nearby-core-ui/src/main/res/drawable/ic_outline_location_on.xml diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt index 225a0e02..d2b831a6 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt @@ -82,6 +82,9 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() { findViewById