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