Loading play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Utils.kt +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import androidx.navigation.NavController import androidx.navigation.navOptions import androidx.navigation.ui.R fun ByteArray.toHexString() : String = joinToString("") { "%02x".format(it) } fun PackageManager.getApplicationInfoIfExists(packageName: String?, flags: Int = 0): ApplicationInfo? = packageName?.let { try { getApplicationInfo(it, flags) Loading play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +7 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ import android.os.Handler import android.os.Looper import android.util.Base64 import android.util.Log import androidx.navigation.fragment.findNavController import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat Loading @@ -27,6 +28,7 @@ import kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { private lateinit var runAttest: Preference private lateinit var runReCaptcha: Preference private lateinit var seeRecent: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet) Loading @@ -36,6 +38,7 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { runAttest = preferenceScreen.findPreference("pref_snet_run_attest") ?: runAttest runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha seeRecent = preferenceScreen.findPreference("pref_snet_recent") ?: seeRecent // TODO: Use SafetyNet client library once ready runAttest.setOnPreferenceClickListener { Loading Loading @@ -124,5 +127,9 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { } true } seeRecent.setOnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openSafetyNetRecentList) true } } } play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetRecentAttestationPreferencesFragment.kt 0 → 100644 +94 −0 Original line number Diff line number Diff line package org.microg.gms.ui import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Bundle import android.text.format.DateUtils import android.widget.Toast import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import org.json.JSONException import org.json.JSONObject import org.microg.gms.firebase.auth.getStringOrNull import org.microg.gms.safetynet.SafetyNetSummary class SafetyNetRecentAttestationPreferencesFragment : PreferenceFragmentCompat() { lateinit var snetSummary: SafetyNetSummary override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_snet_recent_attestation) snetSummary = arguments?.get("summary") as SafetyNetSummary } @SuppressLint("RestrictedApi") override fun onBindPreferences() { val requestType: Preference = preferenceScreen.findPreference("pref_request_type")!! val time : Preference = preferenceScreen.findPreference("pref_time")!! val nonce : Preference = preferenceScreen.findPreference("pref_nonce")!! val status : Preference = preferenceScreen.findPreference("pref_status")!! val evalType : Preference = preferenceScreen.findPreference("pref_eval_type")!! val advice : Preference = preferenceScreen.findPreference("pref_advice")!! val copyResult : Preference = preferenceScreen.findPreference("pref_copy_result")!! requestType.summary = "ATTESTATION" time.summary = DateUtils.getRelativeDateTimeString(context, snetSummary.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME) snetSummary.nonce?.toHexString().let { if (it == null) { nonce.summary = "None" } else { nonce.summary = it } } if(snetSummary.responseStatus==null){ status.summary = "Not completed yet" }else if(snetSummary.responseStatus!!.isSuccess) { try { val json = JSONObject(snetSummary.responseData!!) evalType.summary = json.getString("evaluationType") advice.summary = json.getStringOrNull("advice") ?: "None" val basicIntegrity = json.getBoolean("basicIntegrity") val ctsProfileMatch = json.getBoolean("ctsProfileMatch") status.summary = when{ basicIntegrity && ctsProfileMatch -> { "Integrity and CTS passed" } basicIntegrity -> { "CTS failed" } else -> { "Integrity failed" } } } catch (e: JSONException) { e.printStackTrace() status.summary = "Invalid JSON" } }else{ status.summary = snetSummary.responseStatus!!.statusMessage } copyResult.setOnPreferenceClickListener { val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("JSON JWS data", snetSummary.responseData) clipboard.setPrimaryClip(clip) Toast.makeText(context, "Copied to clipboard !", Toast.LENGTH_SHORT).show() true } } } No newline at end of file play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetRecentFragment.kt 0 → 100644 +61 −0 Original line number Diff line number Diff line package org.microg.gms.ui import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ListView import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.add import androidx.fragment.app.commit import com.google.android.gms.R import com.google.android.gms.databinding.SafetyNetRecentFragmentBinding import org.microg.gms.safetynet.SafetyNetRequestType import org.microg.gms.safetynet.SafetyNetSummary class SafetyNetRecentFragment : Fragment(R.layout.safety_net_recent_fragment) { class MyListView(context: Context) : ListView(context) { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = SafetyNetRecentFragmentBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val summary = arguments?.get("summary") as SafetyNetSummary if(savedInstanceState==null){ parentFragmentManager.commit { setReorderingAllowed(true) if(summary.requestType==SafetyNetRequestType.ATTESTATION){ add<SafetyNetRecentAttestationPreferencesFragment>(R.id.sub_preferences, args=arguments) }else{ add<SafetyNetRecentRecaptchaPreferencesFragment>(R.id.sub_preferences, args=arguments) } } } val pm = requireContext().packageManager val appInfo = pm.getApplicationInfoIfExists(summary.packageName) if(appInfo==null) return Toast.makeText(context, "Application not installed", Toast.LENGTH_SHORT).show() binding.appIcon.setImageDrawable(appInfo.loadIcon(pm)) binding.appName.text = appInfo.loadLabel(pm) binding.packageName.text = summary.packageName } lateinit var binding: SafetyNetRecentFragmentBinding } play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetRecentListFragment.kt 0 → 100644 +65 −0 Original line number Diff line number Diff line package org.microg.gms.ui import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.gms.R import org.microg.gms.safetynet.SafetyNetDatabase class SafetyNetRecentListFragment : Fragment(R.layout.safety_net_recents_list_fragment){ private lateinit var adapter: SafetyNetSummaryAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) val db = SafetyNetDatabase(requireContext()) val recentRequests = db.recentRequests db.close() val recyclerView: RecyclerView = view.findViewById(R.id.snet_recent_recyclerview) if(recentRequests.isEmpty()){ recyclerView.isVisible = false }else{ recyclerView.layoutManager = LinearLayoutManager(context) adapter = SafetyNetSummaryAdapter(recentRequests) { findNavController().navigate(requireContext(), R.id.openSafetyNetRecent, bundleOf( "summary" to it )) } recyclerView.adapter = adapter } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_CLEAR_REQUESTS, 0, R.string.menu_clear_recent_requests) super.onCreateOptionsMenu(menu, inflater) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { MENU_CLEAR_REQUESTS -> { val db = SafetyNetDatabase(requireContext()) db.clearAllRequests() db.close() adapter.clearList() true } else -> super.onOptionsItemSelected(item) } } companion object { private const val MENU_CLEAR_REQUESTS = Menu.FIRST } } Loading
play-services-base-core-ui/src/main/kotlin/org/microg/gms/ui/Utils.kt +2 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,8 @@ import androidx.navigation.NavController import androidx.navigation.navOptions import androidx.navigation.ui.R fun ByteArray.toHexString() : String = joinToString("") { "%02x".format(it) } fun PackageManager.getApplicationInfoIfExists(packageName: String?, flags: Int = 0): ApplicationInfo? = packageName?.let { try { getApplicationInfo(it, flags) Loading
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetPreferencesFragment.kt +7 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ import android.os.Handler import android.os.Looper import android.util.Base64 import android.util.Log import androidx.navigation.fragment.findNavController import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat Loading @@ -27,6 +28,7 @@ import kotlin.random.Random class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { private lateinit var runAttest: Preference private lateinit var runReCaptcha: Preference private lateinit var seeRecent: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_safetynet) Loading @@ -36,6 +38,7 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { override fun onBindPreferences() { runAttest = preferenceScreen.findPreference("pref_snet_run_attest") ?: runAttest runReCaptcha = preferenceScreen.findPreference("pref_recaptcha_run_test") ?: runReCaptcha seeRecent = preferenceScreen.findPreference("pref_snet_recent") ?: seeRecent // TODO: Use SafetyNet client library once ready runAttest.setOnPreferenceClickListener { Loading Loading @@ -124,5 +127,9 @@ class SafetyNetPreferencesFragment : PreferenceFragmentCompat() { } true } seeRecent.setOnPreferenceClickListener { findNavController().navigate(requireContext(), R.id.openSafetyNetRecentList) true } } }
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetRecentAttestationPreferencesFragment.kt 0 → 100644 +94 −0 Original line number Diff line number Diff line package org.microg.gms.ui import android.annotation.SuppressLint import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Bundle import android.text.format.DateUtils import android.widget.Toast import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import org.json.JSONException import org.json.JSONObject import org.microg.gms.firebase.auth.getStringOrNull import org.microg.gms.safetynet.SafetyNetSummary class SafetyNetRecentAttestationPreferencesFragment : PreferenceFragmentCompat() { lateinit var snetSummary: SafetyNetSummary override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_snet_recent_attestation) snetSummary = arguments?.get("summary") as SafetyNetSummary } @SuppressLint("RestrictedApi") override fun onBindPreferences() { val requestType: Preference = preferenceScreen.findPreference("pref_request_type")!! val time : Preference = preferenceScreen.findPreference("pref_time")!! val nonce : Preference = preferenceScreen.findPreference("pref_nonce")!! val status : Preference = preferenceScreen.findPreference("pref_status")!! val evalType : Preference = preferenceScreen.findPreference("pref_eval_type")!! val advice : Preference = preferenceScreen.findPreference("pref_advice")!! val copyResult : Preference = preferenceScreen.findPreference("pref_copy_result")!! requestType.summary = "ATTESTATION" time.summary = DateUtils.getRelativeDateTimeString(context, snetSummary.timestamp, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME) snetSummary.nonce?.toHexString().let { if (it == null) { nonce.summary = "None" } else { nonce.summary = it } } if(snetSummary.responseStatus==null){ status.summary = "Not completed yet" }else if(snetSummary.responseStatus!!.isSuccess) { try { val json = JSONObject(snetSummary.responseData!!) evalType.summary = json.getString("evaluationType") advice.summary = json.getStringOrNull("advice") ?: "None" val basicIntegrity = json.getBoolean("basicIntegrity") val ctsProfileMatch = json.getBoolean("ctsProfileMatch") status.summary = when{ basicIntegrity && ctsProfileMatch -> { "Integrity and CTS passed" } basicIntegrity -> { "CTS failed" } else -> { "Integrity failed" } } } catch (e: JSONException) { e.printStackTrace() status.summary = "Invalid JSON" } }else{ status.summary = snetSummary.responseStatus!!.statusMessage } copyResult.setOnPreferenceClickListener { val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("JSON JWS data", snetSummary.responseData) clipboard.setPrimaryClip(clip) Toast.makeText(context, "Copied to clipboard !", Toast.LENGTH_SHORT).show() true } } } No newline at end of file
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetRecentFragment.kt 0 → 100644 +61 −0 Original line number Diff line number Diff line package org.microg.gms.ui import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ListView import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.add import androidx.fragment.app.commit import com.google.android.gms.R import com.google.android.gms.databinding.SafetyNetRecentFragmentBinding import org.microg.gms.safetynet.SafetyNetRequestType import org.microg.gms.safetynet.SafetyNetSummary class SafetyNetRecentFragment : Fragment(R.layout.safety_net_recent_fragment) { class MyListView(context: Context) : ListView(context) { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = SafetyNetRecentFragmentBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val summary = arguments?.get("summary") as SafetyNetSummary if(savedInstanceState==null){ parentFragmentManager.commit { setReorderingAllowed(true) if(summary.requestType==SafetyNetRequestType.ATTESTATION){ add<SafetyNetRecentAttestationPreferencesFragment>(R.id.sub_preferences, args=arguments) }else{ add<SafetyNetRecentRecaptchaPreferencesFragment>(R.id.sub_preferences, args=arguments) } } } val pm = requireContext().packageManager val appInfo = pm.getApplicationInfoIfExists(summary.packageName) if(appInfo==null) return Toast.makeText(context, "Application not installed", Toast.LENGTH_SHORT).show() binding.appIcon.setImageDrawable(appInfo.loadIcon(pm)) binding.appName.text = appInfo.loadLabel(pm) binding.packageName.text = summary.packageName } lateinit var binding: SafetyNetRecentFragmentBinding }
play-services-core/src/main/kotlin/org/microg/gms/ui/SafetyNetRecentListFragment.kt 0 → 100644 +65 −0 Original line number Diff line number Diff line package org.microg.gms.ui import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.gms.R import org.microg.gms.safetynet.SafetyNetDatabase class SafetyNetRecentListFragment : Fragment(R.layout.safety_net_recents_list_fragment){ private lateinit var adapter: SafetyNetSummaryAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) val db = SafetyNetDatabase(requireContext()) val recentRequests = db.recentRequests db.close() val recyclerView: RecyclerView = view.findViewById(R.id.snet_recent_recyclerview) if(recentRequests.isEmpty()){ recyclerView.isVisible = false }else{ recyclerView.layoutManager = LinearLayoutManager(context) adapter = SafetyNetSummaryAdapter(recentRequests) { findNavController().navigate(requireContext(), R.id.openSafetyNetRecent, bundleOf( "summary" to it )) } recyclerView.adapter = adapter } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { menu.add(0, MENU_CLEAR_REQUESTS, 0, R.string.menu_clear_recent_requests) super.onCreateOptionsMenu(menu, inflater) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { MENU_CLEAR_REQUESTS -> { val db = SafetyNetDatabase(requireContext()) db.clearAllRequests() db.close() adapter.clearList() true } else -> super.onOptionsItemSelected(item) } } companion object { private const val MENU_CLEAR_REQUESTS = Menu.FIRST } }