diff --git a/build.gradle b/build.gradle
index 9cdd3d2e..b3002c34 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,6 +14,7 @@ buildscript {
ext.annotationVersion = '1.1.0'
ext.appcompatVersion = '1.1.0'
+ ext.coreVersion = '1.3.0'
ext.fragmentVersion = '1.2.5'
ext.lifecycleVersion = '2.2.0'
ext.mediarouterVersion = '1.1.0'
diff --git a/play-services-core-proto/build.gradle b/play-services-core-proto/build.gradle
index 1e267ed2..3ae24b59 100644
--- a/play-services-core-proto/build.gradle
+++ b/play-services-core-proto/build.gradle
@@ -19,3 +19,7 @@ wire {
compileKotlin {
kotlinOptions.jvmTarget = 1.8
}
+
+compileTestKotlin {
+ kotlinOptions.jvmTarget = 1.8
+}
diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle
index 523f1017..03a0b700 100644
--- a/play-services-core/build.gradle
+++ b/play-services-core/build.gradle
@@ -26,6 +26,7 @@ configurations {
dependencies {
implementation "com.squareup.wire:wire-runtime:$wireVersion"
implementation "de.hdodenhof:circleimageview:1.3.0"
+ implementation "com.diogobernardino:williamchart:3.7.1"
implementation "org.conscrypt:conscrypt-android:2.1.0"
// TODO: Switch to upstream once raw requests are merged
// https://github.com/vitalidze/chromecast-java-api-v2/pull/99
@@ -40,6 +41,7 @@ dependencies {
implementation project(':firebase-dynamic-links-api')
implementation project(':play-services-base-core')
implementation project(':play-services-location-core')
+ implementation project(':play-services-nearby-core')
implementation project(':play-services-core-proto')
implementation project(':play-services-core:microg-ui-tools') // deprecated
implementation project(':play-services-api')
diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml
index e6353ae6..0b38be30 100644
--- a/play-services-core/src/main/AndroidManifest.xml
+++ b/play-services-core/src/main/AndroidManifest.xml
@@ -107,6 +107,8 @@
android:name="android.permission.UPDATE_APP_OPS_STATS"
tools:ignore="ProtectedPermissions" />
+
+
+
+
+
+
+
+
-
+
diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java
index 05befb86..b98d7525 100644
--- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java
+++ b/play-services-core/src/main/java/org/microg/gms/ui/SettingsActivity.java
@@ -1,5 +1,7 @@
package org.microg.gms.ui;
+import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
@@ -11,6 +13,8 @@ import androidx.navigation.ui.NavigationUI;
import com.google.android.gms.R;
+import org.microg.gms.nearby.exposurenotification.Constants;
+
public class SettingsActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
@@ -21,6 +25,12 @@ public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (Constants.ACTION_EXPOSURE_NOTIFICATION_SETTINGS.equals(intent.getAction()) && intent.getData() == null) {
+ intent.setData(Uri.parse("x-gms-settings://exposure-notifications"));
+ }
+
setContentView(R.layout.settings_root_activity);
appBarConfiguration = new AppBarConfiguration.Builder(getNavController().getGraph()).build();
diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java
index 482152e6..b8dba158 100644
--- a/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java
+++ b/play-services-core/src/main/java/org/microg/gms/ui/SettingsFragment.java
@@ -1,5 +1,6 @@
package org.microg.gms.ui;
+import android.os.Build;
import android.os.Bundle;
import androidx.annotation.Nullable;
@@ -10,6 +11,7 @@ import com.google.android.gms.R;
import org.microg.gms.checkin.CheckinPrefs;
import org.microg.gms.gcm.GcmDatabase;
import org.microg.gms.gcm.GcmPrefs;
+import org.microg.gms.nearby.exposurenotification.ExposurePreferences;
import org.microg.gms.snet.SafetyNetPrefs;
import org.microg.tools.ui.ResourceSettingsFragment;
@@ -20,6 +22,7 @@ public class SettingsFragment extends ResourceSettingsFragment {
public static final String PREF_SNET = "pref_snet";
public static final String PREF_UNIFIEDNLP = "pref_unifiednlp";
public static final String PREF_CHECKIN = "pref_checkin";
+ public static final String PREF_EXPOSURE = "pref_exposure";
public SettingsFragment() {
preferencesResource = R.xml.preferences_start;
@@ -86,6 +89,20 @@ public class SettingsFragment extends ResourceSettingsFragment {
NavHostFragment.findNavController(SettingsFragment.this).navigate(R.id.openUnifiedNlpSettings);
return true;
});
+ if (Build.VERSION.SDK_INT >= 21) {
+ findPreference(PREF_EXPOSURE).setVisible(true);
+ if (new ExposurePreferences(getContext()).getScannerEnabled()) {
+ findPreference(PREF_EXPOSURE).setSummary(getString(R.string.service_status_enabled_short));
+ } else {
+ findPreference(PREF_EXPOSURE).setSummary(R.string.service_status_disabled_short);
+ }
+ findPreference(PREF_EXPOSURE).setOnPreferenceClickListener(preference -> {
+ NavHostFragment.findNavController(SettingsFragment.this).navigate(R.id.openExposureNotificationSettings);
+ return true;
+ });
+ } else {
+ findPreference(PREF_EXPOSURE).setVisible(false);
+ }
boolean checkinEnabled = CheckinPrefs.get(getContext()).isEnabled();
findPreference(PREF_CHECKIN).setSummary(checkinEnabled ? R.string.service_status_enabled_short : R.string.service_status_disabled_short);
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt
index 97bcca1d..4d53f241 100644
--- a/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt
+++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AppIconPreference.kt
@@ -6,15 +6,21 @@
package org.microg.gms.ui
import android.content.Context
+import android.util.AttributeSet
import android.util.DisplayMetrics
import android.widget.ImageView
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
-class AppIconPreference(context: Context) : Preference(context) {
- override fun onBindViewHolder(holder: PreferenceViewHolder?) {
+class AppIconPreference : 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 icon = holder?.findViewById(android.R.id.icon)
+ val icon = holder.findViewById(android.R.id.icon)
if (icon is ImageView) {
icon.adjustViewBounds = true
icon.scaleType = ImageView.ScaleType.CENTER_INSIDE
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt
new file mode 100644
index 00000000..368cf457
--- /dev/null
+++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/BarChartPreference.kt
@@ -0,0 +1,58 @@
+/*
+ * SPDX-FileCopyrightText: 2020, microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.db.williamchart.data.Scale
+import com.db.williamchart.view.BarChartView
+import com.google.android.gms.R
+
+class BarChartPreference : 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)
+
+ init {
+ layoutResource = R.layout.preference_bar_chart
+ }
+
+ private lateinit var chart: BarChartView
+ var labelsFormatter: (Float) -> String = { it.toString() }
+ set(value) {
+ field = value
+ if (this::chart.isInitialized) {
+ chart.labelsFormatter = value
+ }
+ }
+ var scale: Scale? = null
+ set(value) {
+ field = value
+ if (value != null && this::chart.isInitialized) {
+ chart.scale = value
+ }
+ }
+ var data: LinkedHashMap = linkedMapOf()
+ set(value) {
+ field = value
+ if (this::chart.isInitialized) {
+ chart.animate(data)
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ chart = holder.itemView as? BarChartView ?: holder.findViewById(R.id.bar_chart) as BarChartView
+ chart.labelsFormatter = labelsFormatter
+ scale?.let { chart.scale = it }
+ chart.animate(data)
+ }
+
+}
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppFragment.kt
new file mode 100644
index 00000000..bfa544ce
--- /dev/null
+++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppFragment.kt
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: 2020, microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.ui
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import com.google.android.gms.R
+import com.google.android.gms.databinding.ExposureNotificationsAppFragmentBinding
+import com.google.android.gms.databinding.ExposureNotificationsFragmentBinding
+import org.microg.gms.nearby.exposurenotification.ExposurePreferences
+
+class ExposureNotificationsAppFragment : Fragment(R.layout.exposure_notifications_app_fragment) {
+ private lateinit var binding: ExposureNotificationsAppFragmentBinding
+ val packageName: String?
+ get() = arguments?.getString("package")
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ binding = ExposureNotificationsAppFragmentBinding.inflate(inflater, container, false)
+ binding.callbacks = object : ExposureNotificationsAppFragmentCallbacks {
+ override fun onAppClicked() {
+ val intent = Intent()
+ intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+ val uri: Uri = Uri.fromParts("package", packageName, null)
+ intent.data = uri
+ context!!.startActivity(intent)
+ }
+ }
+ childFragmentManager.findFragmentById(R.id.sub_preferences)?.arguments = arguments
+ return binding.root
+ }
+
+ override fun onResume() {
+ super.onResume()
+ lifecycleScope.launchWhenResumed {
+ val pm = requireContext().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)
+ }
+ }
+}
+
+interface ExposureNotificationsAppFragmentCallbacks {
+ fun onAppClicked()
+}
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt
new file mode 100644
index 00000000..2b1c7c55
--- /dev/null
+++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsAppPreferencesFragment.kt
@@ -0,0 +1,60 @@
+/*
+ * SPDX-FileCopyrightText: 2020, microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.format.DateUtils
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import com.google.android.gms.R
+import org.microg.gms.nearby.exposurenotification.ExposureDatabase
+
+class ExposureNotificationsAppPreferencesFragment : PreferenceFragmentCompat() {
+ private lateinit var open: Preference
+ private lateinit var checks: Preference
+ private val packageName: String?
+ get() = arguments?.getString("package")
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ addPreferencesFromResource(R.xml.preferences_exposure_notifications_app)
+ }
+
+ override fun onBindPreferences() {
+ open = preferenceScreen.findPreference("pref_exposure_app_open") ?: open
+ checks = preferenceScreen.findPreference("pref_exposure_app_checks") ?: checks
+ open.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ try {
+ packageName?.let {
+ context?.packageManager?.getLaunchIntentForPackage(it)?.let { intent ->
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context?.startActivity(intent)
+ }
+ }
+ } catch (ignored: Exception) {
+ }
+ true
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateContent()
+ }
+
+ fun updateContent() {
+ packageName?.let { packageName ->
+ val database = ExposureDatabase(requireContext())
+ var str = getString(R.string.pref_exposure_app_checks_summary, database.countMethodCalls(packageName, "provideDiagnosisKeys"))
+ val lastCheckTime = database.lastMethodCall(packageName, "provideDiagnosisKeys")
+ if (lastCheckTime != null && lastCheckTime != 0L) {
+ str += "\n" + getString(R.string.pref_exposure_app_last_check_summary, DateUtils.getRelativeDateTimeString(context, lastCheckTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME))
+ }
+ checks.summary = str
+ database.close()
+ }
+ }
+}
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt
new file mode 100644
index 00000000..3b2988c7
--- /dev/null
+++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/ExposureNotificationsConfirmActivity.kt
@@ -0,0 +1,68 @@
+/*
+ * SPDX-FileCopyrightText: 2020, microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.ui
+
+import android.os.Bundle
+import android.os.ResultReceiver
+import android.widget.Button
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.google.android.gms.R
+import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.*
+import org.microg.gms.nearby.exposurenotification.*
+
+class ExposureNotificationsConfirmActivity : AppCompatActivity() {
+ private var resultCode: Int = FAILED
+ private val resultData: Bundle = Bundle()
+ private val receiver: ResultReceiver?
+ get() = intent.getParcelableExtra(KEY_CONFIRM_RECEIVER)
+ private val action: String?
+ get() = intent.getStringExtra(KEY_CONFIRM_ACTION)
+ private val targetPackageName: String?
+ get() = intent.getStringExtra(KEY_CONFIRM_PACKAGE)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.exposure_notifications_confirm_activity)
+ val applicationInfo = packageManager.getApplicationInfoIfExists(targetPackageName)
+ when (action) {
+ CONFIRM_ACTION_START -> {
+ findViewById(android.R.id.title).text = getString(R.string.exposure_confirm_start_title)
+ findViewById(android.R.id.summary).text = getString(R.string.exposure_confirm_start_summary, applicationInfo?.loadLabel(packageManager)
+ ?: targetPackageName)
+ findViewById
None
See all
+ Open
+
Google Play Games
%1$s would like to use Play Games
To use Play Games it is required to install the Google Play Games app. The application might continue without Play Games, but it is possible that it will behave unexpectedly.
@@ -140,6 +143,40 @@ This can take a couple of minutes."
Not registered
Last registration: %1$s
+ Register device
+
+ To enable Exposure Notifications, open any app supporting it.
+ Apps using Exposure Notifications
+ Collected IDs
+ %1$d IDs in last hour
+ Currently broadcasted ID
+ %1$d checks in past 14 days
+ Last check: %1$s
+ %1$d IDs collected
+ Delete all collected IDs
+ "Exposure Notifications API allows apps to notify you if you were exposed to someone who reported to be diagnosed positive.
+
+The date, duration, and signal strength associated with an exposure will be shared with the corresponding app."
+ "While Exposure Notification API is enabled, your device passively collects IDs (called Rolling Proximity Identifiers, or RPIs) from nearby devices.
+
+When device owners report to be diagnosed positive, their IDs can be shared. Your device checks if any of the known diagnosed IDs matches any of the collected IDs and calculates your infection risk."
+
+ Use Exposure Notifications
+ Turn on Exposure Notifications?
+ "Your phone needs to use Bluetooth to securely collect and share IDs with other phones that are nearby.
+
+%1$s can notify you if you were exposed to someone who reported to be diagnosed positive.
+
+The date, duration, and signal strength associated with an exposure will be shared with the app."
+ Turn on
+ Turn off Exposure Notifications?
+ After disabling Exposure Notifications, you will no longer be notified when you were exposed to someone who reported to be diagnosed positive.
+ Turn off
+ Share your IDs with %1$s?
+ "Your IDs from the last 14 days will be used to help notify others that you've been near about potential exposure.
+
+Your identity or test result won't be shared with other people."
+ Share
Status
More
@@ -172,6 +209,7 @@ This can take a couple of minutes."
Messages: %1$d (%2$d bytes)
Disconnected
Connected since %1$s
+ Receive push notifications
Allow registration
Allow the app to register for push notifications.
@@ -183,6 +221,7 @@ This can take a couple of minutes."
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.
+ Allow device attestation
Try SafetyNet attestation
diff --git a/play-services-core/src/main/res/values/themes.xml b/play-services-core/src/main/res/values/themes.xml
index 27c0a15c..6d728832 100644
--- a/play-services-core/src/main/res/values/themes.xml
+++ b/play-services-core/src/main/res/values/themes.xml
@@ -16,6 +16,11 @@
+
+