EN: Handle confirmation via resolution/pending intent instead of new task

This commit is contained in:
Marvin W 2020-09-05 23:51:00 +02:00
parent fd6d915f0a
commit d33391ebce
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
4 changed files with 82 additions and 50 deletions

View File

@ -11,12 +11,14 @@ 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 var resultCode: Int = RESULT_CANCELED
set(value) {
setResult(value)
field = value
}
private val receiver: ResultReceiver?
get() = intent.getParcelableExtra(KEY_CONFIRM_RECEIVER)
private val action: String?
@ -47,22 +49,22 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() {
findViewById<Button>(android.R.id.button1).text = getString(R.string.exposure_confirm_keys_button)
}
else -> {
resultCode = INTERNAL_ERROR
resultCode = RESULT_CANCELED
finish()
}
}
findViewById<Button>(android.R.id.button1).setOnClickListener {
resultCode = SUCCESS
resultCode = RESULT_OK
finish()
}
findViewById<Button>(android.R.id.button2).setOnClickListener {
resultCode = FAILED_REJECTED_OPT_IN
resultCode = RESULT_CANCELED
finish()
}
}
override fun onStop() {
super.onStop()
receiver?.send(resultCode, resultData)
override fun finish() {
receiver?.send(resultCode, Bundle())
super.finish()
}
}

View File

@ -25,6 +25,7 @@ const val KEY_CONFIRM_PACKAGE = "package"
const val CONFIRM_ACTION_START = "start"
const val CONFIRM_ACTION_STOP = "stop"
const val CONFIRM_ACTION_KEYS = "keys"
const val CONFIRM_PERMISSION_VALIDITY = 60 * 60 * 1000L
const val PERMISSION_EXPOSURE_CALLBACK = "com.google.android.gms.nearby.exposurenotification.EXPOSURE_CALLBACK"

View File

@ -54,6 +54,9 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_DIAGNOSIS(package TEXT NOT NULL, token TEXT NOT NULL, tcid INTEGER REFERENCES $TABLE_TEK_CHECK(tcid) ON DELETE CASCADE, transmissionRiskLevel INTEGER NOT NULL);")
db.execSQL("CREATE INDEX IF NOT EXISTS index_${TABLE_DIAGNOSIS}_package_token ON $TABLE_DIAGNOSIS(package, token);")
}
if (oldVersion < 3) {
db.execSQL("CREATE TABLE $TABLE_APP_PERMS(package TEXT NOT NULL, sig TEXT NOT NULL, perm TEXT NOT NULL, timestamp INTEGER NOT NULL);")
}
}
fun SQLiteDatabase.delete(table: String, whereClause: String, args: LongArray): Int =
@ -68,7 +71,23 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
val appLogEntries = delete(TABLE_APP_LOG, "timestamp < ?", longArrayOf(rollingStartTime))
val temporaryExposureKeys = delete(TABLE_TEK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
val checkedTemporaryExposureKeys = delete(TABLE_TEK_CHECK, "(rollingStartNumber + rollingPeriod) < ?", longArrayOf(rollingStartTime / ROLLING_WINDOW_LENGTH_MS))
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv, $appLogEntries applogs, $temporaryExposureKeys teks, $checkedTemporaryExposureKeys cteks")
val appPerms = delete(TABLE_APP_PERMS, "timestamp < ?", longArrayOf(System.currentTimeMillis() - CONFIRM_PERMISSION_VALIDITY))
Log.d(TAG, "Deleted on daily cleanup: $advertisements adv, $appLogEntries applogs, $temporaryExposureKeys teks, $checkedTemporaryExposureKeys cteks, $appPerms perms")
}
fun grantPermission(packageName: String, signatureDigest: String, permission: String, timestamp: Long = System.currentTimeMillis()) = writableDatabase.run {
insert(TABLE_APP_PERMS, "NULL", ContentValues().apply {
put("package", packageName)
put("sig", signatureDigest)
put("perm", permission)
put("timestamp", timestamp)
})
}
fun hasPermission(packageName: String, signatureDigest: String, permission: String, maxAge: Long = CONFIRM_PERMISSION_VALIDITY) = readableDatabase.run {
query(TABLE_APP_PERMS, arrayOf("MAX(timestamp)"), "package = ? AND sig = ? and perm = ?", arrayOf(packageName, signatureDigest, permission), null, null, null).use { cursor ->
cursor.moveToNext() && cursor.getLong(0) + maxAge > System.currentTimeMillis()
}
}
fun noteAdvertisement(rpi: ByteArray, aem: ByteArray, rssi: Int, timestamp: Long = Date().time) = writableDatabase.run {
@ -496,13 +515,14 @@ class ExposureDatabase private constructor(private val context: Context) : SQLit
companion object {
private const val DB_NAME = "exposure.db"
private const val DB_VERSION = 2
private const val DB_VERSION = 3
private const val TABLE_ADVERTISEMENTS = "advertisements"
private const val TABLE_APP_LOG = "app_log"
private const val TABLE_TEK = "tek"
private const val TABLE_TEK_CHECK = "tek_check"
private const val TABLE_DIAGNOSIS = "diagnosis"
private const val TABLE_CONFIGURATIONS = "configurations"
private const val TABLE_APP_PERMS = "app_perms"
private var instance: ExposureDatabase? = null
fun ref(context: Context): ExposureDatabase = synchronized(this) {

View File

@ -5,6 +5,8 @@
package org.microg.gms.nearby.exposurenotification
import android.app.Activity
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@ -13,12 +15,14 @@ import android.os.*
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.nearby.exposurenotification.ExposureNotificationStatusCodes
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.*
import com.google.android.gms.nearby.exposurenotification.ExposureSummary
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import com.google.android.gms.nearby.exposurenotification.internal.*
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.TemporaryExposureKeyExport
import org.microg.gms.nearby.exposurenotification.proto.TemporaryExposureKeyProto
@ -26,48 +30,51 @@ import java.util.*
import java.util.zip.ZipInputStream
class ExposureNotificationServiceImpl(private val context: Context, private val packageName: String) : INearbyExposureNotificationService.Stub() {
private fun confirm(action: String, callback: (resultCode: Int, resultData: Bundle?) -> Unit) {
private fun pendingConfirm(permission: String): PendingIntent {
val intent = Intent(ACTION_CONFIRM)
intent.`package` = context.packageName
intent.putExtra(KEY_CONFIRM_PACKAGE, packageName)
intent.putExtra(KEY_CONFIRM_ACTION, action)
intent.putExtra(KEY_CONFIRM_RECEIVER, object : ResultReceiver(Handler(Looper.getMainLooper())) {
intent.putExtra(KEY_CONFIRM_ACTION, permission)
intent.putExtra(KEY_CONFIRM_RECEIVER, object : ResultReceiver(null) {
override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
Log.d(TAG, "Result from action $action: ${getStatusCodeString(resultCode)}")
callback(resultCode, resultData)
if (resultCode == Activity.RESULT_OK) {
ExposureDatabase.with(context) { database -> database.grantPermission(packageName, PackageUtils.firstSignatureDigest(context, packageName)!!, permission) }
}
}
})
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
intent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
try {
intent.component = ComponentName(context, context.packageManager.resolveActivity(intent, 0)?.activityInfo?.name!!)
context.startActivity(intent)
} catch (e: Exception) {
Log.w(TAG, e)
callback(CommonStatusCodes.INTERNAL_ERROR, null)
}
Log.d(TAG, "Pending: $intent")
val pi = PendingIntent.getActivity(context, permission.hashCode(), intent, PendingIntent.FLAG_ONE_SHOT)
Log.d(TAG, "Pending: $pi")
return pi
}
private fun confirmPermission(permission: String): Status {
if (packageName == context.packageName) return Status.SUCCESS
return ExposureDatabase.with(context) { database ->
if (!database.hasPermission(packageName, PackageUtils.firstSignatureDigest(context, packageName)!!, permission)) {
Status(RESOLUTION_REQUIRED, "Permission EN#$permission required.", pendingConfirm(permission))
} else {
Status.SUCCESS
}
}
}
override fun start(params: StartParams) {
if (ExposurePreferences(context).enabled) {
params.callback.onResult(Status(FAILED_ALREADY_STARTED))
return
if (ExposurePreferences(context).enabled) return
val status = confirmPermission(CONFIRM_ACTION_START)
if (status.isSuccess) {
ExposurePreferences(context).enabled = true
ExposureDatabase.with(context) { database -> database.noteAppAction(packageName, "start") }
}
confirm(CONFIRM_ACTION_START) { resultCode, resultData ->
if (resultCode == SUCCESS) {
ExposurePreferences(context).enabled = true
}
ExposureDatabase.with(context) { database ->
database.noteAppAction(packageName, "start", JSONObject().apply {
put("result", resultCode)
}.toString())
}
try {
params.callback.onResult(Status(if (resultCode == SUCCESS) SUCCESS else FAILED_REJECTED_OPT_IN, resultData?.getString("message")))
} catch (e: Exception) {
Log.w(TAG, "Callback failed", e)
}
try {
params.callback.onResult(status)
} catch (e: Exception) {
Log.w(TAG, "Callback failed", e)
}
}
@ -91,23 +98,25 @@ class ExposureNotificationServiceImpl(private val context: Context, private val
}
}
override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams): Unit = ExposureDatabase.with(context) { database ->
confirm(CONFIRM_ACTION_START) { resultCode, resultData ->
val (status, response) = if (resultCode == SUCCESS) {
SUCCESS to database.allKeys
} else {
FAILED_REJECTED_OPT_IN to emptyList()
override fun getTemporaryExposureKeyHistory(params: GetTemporaryExposureKeyHistoryParams) {
val status = confirmPermission(CONFIRM_ACTION_KEYS)
val response = when {
status.isSuccess -> ExposureDatabase.with(context) { database ->
database.allKeys
}
else -> emptyList()
}
ExposureDatabase.with(context) { database ->
database.noteAppAction(packageName, "getTemporaryExposureKeyHistory", JSONObject().apply {
put("result", resultCode)
put("result", status.statusCode)
put("response_keys_size", response.size)
}.toString())
try {
params.callback.onResult(Status(status, resultData?.getString("message")), response)
} catch (e: Exception) {
Log.w(TAG, "Callback failed", e)
}
}
try {
params.callback.onResult(status, response)
} catch (e: Exception) {
Log.w(TAG, "Callback failed", e)
}
}