diff --git a/app/build.gradle b/app/build.gradle index 42e3e8050..e0080c707 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,6 +60,7 @@ android { buildConfigField 'String', 'BASE_SOFTWARE_NAME', '"ooniprobe-android-dev"' resValue "string", "APP_ID", 'org.openobservatory.ooniprobe.dev' resValue "string", "APP_NAME", "OONI Dev" + buildConfigField 'String', 'OONI_API_BASE_URL', '"https://api.dev.ooni.io"' buildConfigField 'String', 'COUNTLY_KEY', '"e6c2cfe53e85951d50567467cef3f9fa2eab32c3"' } experimental { @@ -70,6 +71,7 @@ android { buildConfigField 'String', 'BASE_SOFTWARE_NAME', '"ooniprobe-android-experimental"' resValue "string", "APP_ID", 'org.openobservatory.ooniprobe.experimental' resValue "string", "APP_NAME", "OONI Exp" + buildConfigField 'String', 'OONI_API_BASE_URL', '"https://api.dev.ooni.io"' buildConfigField 'String', 'COUNTLY_KEY', '"e6c2cfe53e85951d50567467cef3f9fa2eab32c3"' } fdroid { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java index ad681dbaa..ad1e5f07f 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java @@ -48,6 +48,7 @@ import java.io.Serializable; import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Locale; import java.util.Objects; @@ -55,7 +56,7 @@ import io.noties.markwon.Markwon; -public class OverviewActivity extends ReviewUpdatesAbstractActivity implements ConfirmDialogFragment.OnClickListener { +public class OverviewActivity extends ReviewUpdatesAbstractActivity implements ConfirmDialogFragment.OnClickListener { private static final String TEST = "test"; ActivityOverviewBinding binding; @@ -104,10 +105,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { String.format( "Created by %s on %s\n\n%s", testDescriptor.getAuthor(), - new SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH).format(testDescriptor.getDescriptorCreationTime()), + new SimpleDateFormat("MMM dd, yyyy", Locale.ENGLISH).format(testDescriptor.getDateCreated()), descriptor.getDescription() ) ); + if (Boolean.TRUE.equals(testDescriptor.isExpired())) { + binding.expiredTag.getRoot().setVisibility(View.VISIBLE); + } } else { markwon.setMarkdown(binding.desc, descriptor.getDescription()); } @@ -258,13 +262,17 @@ private void onManualUpdatesFetchComplete(WorkInfo workInfo) { if (workInfo != null) { switch (workInfo.getState()) { case SUCCEEDED -> { - binding.reviewUpdates.setVisibility(View.VISIBLE); - binding.reviewUpdates.setOnClickListener(view -> getReviewUpdatesLauncher().launch( - ReviewDescriptorUpdatesActivity.newIntent( - OverviewActivity.this, - workInfo.getOutputData().getString(ManualUpdateDescriptorsWorker.KEY_UPDATED_DESCRIPTORS) - ) - )); + + String descriptor = workInfo.getOutputData().getString(ManualUpdateDescriptorsWorker.KEY_UPDATED_DESCRIPTORS); + if (descriptor != null && !descriptor.isEmpty()) { + binding.reviewUpdates.setVisibility(View.VISIBLE); + binding.reviewUpdates.setOnClickListener(view -> getReviewUpdatesLauncher().launch( + ReviewDescriptorUpdatesActivity.newIntent( + OverviewActivity.this, + descriptor + ) + )); + } binding.swipeRefresh.setRefreshing(false); } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt index ebe32bd45..824d070fc 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt @@ -226,7 +226,7 @@ class DescriptorUpdateFragment : Fragment() { SimpleDateFormat( "MMM dd, yyyy", Locale.getDefault() - ).format(descriptor.descriptorCreationTime) + ).format(descriptor.dateCreated) }" description.text = absDescriptor.description // Use markdown icon.setImageResource(absDescriptor.getDisplayIcon(requireContext())) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt index dbbda96ae..97f286759 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt @@ -1,18 +1,15 @@ package org.openobservatory.ooniprobe.adapters -import android.content.res.Resources -import android.graphics.PorterDuff import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.cardview.widget.CardView import androidx.recyclerview.widget.RecyclerView -import org.openobservatory.ooniprobe.R import org.openobservatory.ooniprobe.common.AbstractDescriptor import org.openobservatory.ooniprobe.common.PreferenceManager import org.openobservatory.ooniprobe.databinding.ItemSeperatorBinding import org.openobservatory.ooniprobe.databinding.ItemTestsuiteBinding import org.openobservatory.ooniprobe.model.database.InstalledDescriptor +import java.util.Date class DashboardAdapter( private val items: List, @@ -62,6 +59,9 @@ class DashboardAdapter( icon.setImageResource(item.getDisplayIcon(holder.itemView.context)).also { if (item is InstalledDescriptor){ icon.setColorFilter(item.color) + if (item.descriptor?.isExpired == true) { + expiredTag.root.visibility = View.VISIBLE + } holder.setIsRecyclable(false) } } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/OONIDescriptor.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/OONIDescriptor.kt index bd1374611..7205d1395 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/OONIDescriptor.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/OONIDescriptor.kt @@ -33,27 +33,6 @@ abstract class AbstractDescriptor( open var descriptor: TestDescriptor? = null ) : BaseDescriptor(name = name, nettests = nettests) { - /** - * This function is used to determine if the current descriptor is enabled. - * If the descriptor is [OONITests.EXPERIMENTAL], this function returns the preference value stored for the `experimental` preference key. - * If the descriptor is [OONITests.WEBSITES], this function returns true if at least one category is enabled in the preferences. - * Otherwise, it checks if any of the nettests are enabled based on the preferences stored in the provided [PreferenceManager]. - * - * @param preferenceManager The [PreferenceManager] instance used to resolve the status of each nettest. - * @return Boolean Returns true if at least one nettest is enabled, false otherwise. - */ - open fun isEnabled(preferenceManager: PreferenceManager): Boolean { - return when (name) { - OONITests.EXPERIMENTAL.label -> preferenceManager.isExperimentalOn - else -> nettests.any { - preferenceManager.resolveStatus( - name = it.name, - prefix = preferencePrefix(), - ) - } - } - } - /** * Returns the runtime of the current descriptor. * diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/TestDescriptorManager.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/TestDescriptorManager.kt index 338ec6249..54253b94a 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/TestDescriptorManager.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/TestDescriptorManager.kt @@ -4,7 +4,7 @@ import android.content.Context import com.raizlabs.android.dbflow.sql.language.SQLite import org.openobservatory.engine.BaseNettest import org.openobservatory.engine.LoggerArray -import org.openobservatory.engine.OONIRunFetchResponse +import org.openobservatory.engine.OONIRunDescriptor import org.openobservatory.ooniprobe.BuildConfig import org.openobservatory.ooniprobe.activity.adddescriptor.adapter.GroupedItem import org.openobservatory.ooniprobe.model.database.InstalledDescriptor @@ -59,23 +59,26 @@ class TestDescriptorManager @Inject constructor( ) val ooniContext = session.newContextWithTimeout(300) - val response: OONIRunFetchResponse = session.ooniRunFetch(ooniContext, runId) + val response: OONIRunDescriptor = + session.ooniRunFetch(ooniContext, BuildConfig.OONI_API_BASE_URL, runId) return TestDescriptor( runId = runId, - name = response.descriptor.name, - nameIntl = response.descriptor.nameIntl, - author = response.descriptor.author, - shortDescription = response.descriptor.shortDescription, - shortDescriptionIntl = response.descriptor.shortDescriptionIntl, - description = response.descriptor.description, - descriptionIntl = response.descriptor.descriptionIntl, - icon = response.descriptor.icon, - color = response.descriptor.color, - animation = response.descriptor.animation, - isArchived = response.archived, - descriptorCreationTime = response.creationTime, - translationCreationTime = response.translationCreationTime, - nettests = response.descriptor.nettests + name = response.name, + shortDescription = response.shortDescription, + description = response.description, + author = response.author, + nettests = response.nettests, + nameIntl = response.nameIntl, + shortDescriptionIntl = response.shortDescriptionIntl, + descriptionIntl = response.descriptionIntl, + icon = response.icon, + color = response.color, + animation = response.animation, + expirationDate = response.expirationDate, + dateCreated = response.dateCreated, + dateUpdated = response.dateUpdated, + revision = response.revision, + isExpired = response.isExpired ) } @@ -107,11 +110,11 @@ class TestDescriptorManager @Inject constructor( fun getRunV2Descriptors(): List { return SQLite.select().from(TestDescriptor::class.java) - .where(TestDescriptor_Table.isArchived.eq(false)).queryList() + .orderBy(TestDescriptor_Table.is_expired.asc()).queryList() } fun delete(descriptor: InstalledDescriptor): Boolean { - preferenceManager.sp.all.entries.forEach {entry -> + preferenceManager.sp.all.entries.forEach { entry -> if (entry.key.contains(descriptor.testDescriptor.runId.toString())) { preferenceManager.sp.edit().remove(entry.key).apply() } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/worker/UpdateDescriptorsWorker.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/worker/UpdateDescriptorsWorker.kt index ae9d7135b..d861c00c1 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/worker/UpdateDescriptorsWorker.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/worker/UpdateDescriptorsWorker.kt @@ -101,11 +101,11 @@ class ManualUpdateDescriptorsWorker( val descriptors = inputData.getLongArray(KEY_DESCRIPTOR_IDS)?.let { d.testDescriptorManager.getDescriptorsFromIds(it.toTypedArray()) - }.run { + } ?: run { d.testDescriptorManager.getDescriptorWithAutoUpdateDisabled() } - if(descriptors.isEmpty()) { + if (descriptors.isEmpty()) { Log.e(TAG, "No descriptors to update") return Result.success() } @@ -135,7 +135,12 @@ class ManualUpdateDescriptorsWorker( Log.e(TAG, "fetching updates complete") setProgressAsync(Data.Builder().putInt(PROGRESS, 100).build()) - Result.success(outputData) + if (updatedDescriptors.isEmpty()) { + Log.e(TAG, "No descriptors were updated") + Result.success() + } else { + Result.success(outputData) + } } catch (exception: Exception) { Log.e(TAG, "Error Updating") diff --git a/app/src/main/java/org/openobservatory/ooniprobe/model/database/TestDescriptor.kt b/app/src/main/java/org/openobservatory/ooniprobe/model/database/TestDescriptor.kt index daef1ca8e..555dd24a7 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/model/database/TestDescriptor.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/model/database/TestDescriptor.kt @@ -25,40 +25,68 @@ import com.raizlabs.android.dbflow.annotation.TypeConverter as TypeConverterAnno @Table(database = AppDatabase::class) class TestDescriptor( + @PrimaryKey var runId: Long = 0, + @Column var name: String = "", - @Column(name = "name_intl", typeConverter = MapConverter::class) - var nameIntl: Any? = null, - @Column - var author: String = "", + @Column(name = "short_description") var shortDescription: String = "", - @Column(name = "short_description_intl", typeConverter = MapConverter::class) - var shortDescriptionIntl: Any? = null, + @Column var description: String = "", + + @Column + var author: String = "", + + @Column(typeConverter = NettestConverter::class) + var nettests: Any = emptyList(), + + @Column(name = "name_intl", typeConverter = MapConverter::class) + var nameIntl: Any? = null, + + @Column(name = "short_description_intl", typeConverter = MapConverter::class) + var shortDescriptionIntl: Any? = null, + @Column(name = "description_intl", typeConverter = MapConverter::class) var descriptionIntl: Any? = null, + @Column var icon: String? = null, + @Column var color: String? = null, + @Column var animation: String? = null, + + @Column(name = "expiration_date") + var expirationDate: Date? = null, + + @Column(name = "date_created") + var dateCreated: Date? = null, + + @Column(name = "date_updated") + var dateUpdated: Date? = null, + @Column - var isArchived: Boolean = false, + var revision: String? = null, + + @Column(name = "is_expired") + var isExpired: Boolean? = false, + + // TODO(aanorbel): figure out whether we should remove this field. + //@Column + //var isArchived: Boolean = false, + @Column(name = "auto_run") var isAutoRun: Boolean = true, + @Column(name = "auto_update") var isAutoUpdate: Boolean = false, - @Column(name = "descriptor_creation_time") - var descriptorCreationTime: Date? = null, - @Column(name = "translation_creation_time") - var translationCreationTime: Date? = null, - @Column(typeConverter = NettestConverter::class) - var nettests: Any = emptyList() + ) : BaseModel(), Serializable { fun preferencePrefix(): String { return "${runId}_" @@ -66,14 +94,12 @@ class TestDescriptor( } /** - * Check if the descriptor should be updated based on the creation time of the descriptor and the creation time of the translations. - * - * @param updatedDescriptor The updated descriptor - * @return True if the descriptor should be updated based on the creation time of the descriptor and the creation time of the translations. + * Check if the test descriptor should be updated + * @param updatedDescriptor The new descriptor + * @return True if the descriptor should be updated, False otherwise */ fun TestDescriptor.shouldUpdate(updatedDescriptor: TestDescriptor): Boolean { - return (updatedDescriptor.descriptorCreationTime?.after(descriptorCreationTime) ?: true - || updatedDescriptor.translationCreationTime?.after(translationCreationTime) ?: true) + return (updatedDescriptor.dateUpdated?.after(dateUpdated) ?: true) } private const val DESCRIPTOR_TEST_NAME = "ooni_run" @@ -103,10 +129,6 @@ class InstalledDescriptor( }, descriptor = testDescriptor) { - override fun isEnabled(preferenceManager: PreferenceManager): Boolean { - return !testDescriptor.isArchived - } - override fun getRuntime(context: Context, preferenceManager: PreferenceManager): Int { return R.string.TestResults_NotAvailable } @@ -190,15 +212,18 @@ class ITestDescriptor( var name: String = "", - var nameIntl: HashMap? = null, + var shortDescription: String = "", + + var description: String = "", var author: String = "", - var shortDescription: String = "", - var shortDescriptionIntl: HashMap? = null, + var nettests: List? = emptyList(), - var description: String = "", + var nameIntl: HashMap? = null, + + var shortDescriptionIntl: HashMap? = null, var descriptionIntl: HashMap? = null, @@ -208,37 +233,39 @@ class ITestDescriptor( var animation: String? = null, - var isArchived: Boolean = false, + val expirationDate: Date? = null, - var isAutoRun: Boolean = true, + val dateCreated: Date? = null, - var isAutoUpdate: Boolean = false, + val dateUpdated: Date? = null, + + val revision: String? = null, - var descriptorCreationTime: Date? = null, + val isExpired: Boolean? = false, - var translationCreationTime: Date? = null, + var isAutoUpdate: Boolean = false, - var nettests: List? = emptyList() ) : Serializable { fun toTestDescriptor(): TestDescriptor { return TestDescriptor( runId = runId, name = name, - nameIntl = nameIntl, - author = author, shortDescription = shortDescription, - shortDescriptionIntl = shortDescriptionIntl, description = description, + author = author, + nettests = nettests?: emptyList(), + nameIntl = nameIntl, + shortDescriptionIntl = shortDescriptionIntl, descriptionIntl = descriptionIntl, icon = icon, color = color, animation = animation, - isArchived = isArchived, - isAutoRun = isAutoRun, - isAutoUpdate = isAutoUpdate, - descriptorCreationTime = descriptorCreationTime, - translationCreationTime = translationCreationTime, - nettests = nettests ?: emptyList() + expirationDate = expirationDate, + dateCreated = dateCreated, + dateUpdated = dateUpdated, + revision = revision, + isExpired = isExpired, + isAutoUpdate = isAutoUpdate ) } } diff --git a/app/src/main/res/drawable/expired.xml b/app/src/main/res/drawable/expired.xml new file mode 100644 index 000000000..6ad7a7bec --- /dev/null +++ b/app/src/main/res/drawable/expired.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/layout/activity_overview.xml b/app/src/main/res/layout/activity_overview.xml index fd0140a75..baa974034 100644 --- a/app/src/main/res/layout/activity_overview.xml +++ b/app/src/main/res/layout/activity_overview.xml @@ -87,6 +87,11 @@ + +