Skip to content

Commit

Permalink
feat (OONI Run v2): Add support for revisions. (#718)
Browse files Browse the repository at this point in the history
Fixes Revision Support. Part of  ooni/run#155

## Proposed Changes

  - Fetch previous revisions while fetching Descriptor.
  - Display revision in `Update Review` layout

|.|.|
|-|-|
|
![Screenshot_20240410_143228](https://github.com/ooni/probe-android/assets/17911892/26a19475-a314-4a30-add5-aab47a9cc9f7)
|
![Screenshot_20240410_143253](https://github.com/ooni/probe-android/assets/17911892/ef75cadd-4bb8-471c-8d71-93cf7359465e)
|
<!---
|
![Screenshot_20240410_120122](https://github.com/ooni/probe-android/assets/17911892/2841eab2-5a8e-4767-9d34-98e9d5cb7867)
|
![Screenshot_20240410_120140](https://github.com/ooni/probe-android/assets/17911892/bd90d741-2326-4cd5-9fe4-1ca356e63d92)
|
-->
  • Loading branch information
aanorbel authored Apr 12, 2024
1 parent 5b7a6e9 commit 13ddead
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.openobservatory.ooniprobe.activity.customwebsites.CustomWebsiteActivity;
import org.openobservatory.ooniprobe.activity.overview.OverviewTestsExpandableListViewAdapter;
import org.openobservatory.ooniprobe.activity.overview.OverviewViewModel;
import org.openobservatory.ooniprobe.activity.overview.RevisionsFragment;
import org.openobservatory.ooniprobe.activity.reviewdescriptorupdates.ReviewDescriptorUpdatesActivity;
import org.openobservatory.ooniprobe.common.AbstractDescriptor;
import org.openobservatory.ooniprobe.common.OONITests;
Expand All @@ -48,7 +49,6 @@

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;

Expand Down Expand Up @@ -163,7 +163,22 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
if (descriptor instanceof InstalledDescriptor) {
binding.uninstallLink.setVisibility(View.VISIBLE);
binding.automaticUpdatesContainer.setVisibility(View.VISIBLE);
binding.automaticUpdatesSwitch.setChecked(((InstalledDescriptor) descriptor).getTestDescriptor().isAutoUpdate());
InstalledDescriptor installedDescriptor = (InstalledDescriptor) descriptor;
binding.automaticUpdatesSwitch.setChecked(installedDescriptor.getTestDescriptor().isAutoUpdate());

try {
if (Integer.parseInt(installedDescriptor.getTestDescriptor().getRevision()) > 1) {
getSupportFragmentManager().beginTransaction().add(
binding.revisionsContainer.getId(),
RevisionsFragment.newInstance(
installedDescriptor.getDescriptor().getRunId(),
installedDescriptor.getDescriptor().getPreviousRevision()
)
).commit();
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
binding.uninstallLink.setVisibility(View.GONE);
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class AddDescriptorActivity : AbstractActivity() {
}

binding.btnCancel.setOnClickListener {
Toast.makeText(this@AddDescriptorActivity, "Link installation cancelled", Toast.LENGTH_LONG).show()
finish()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package org.openobservatory.ooniprobe.activity.overview

import android.content.Intent
import android.graphics.Paint
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import org.openobservatory.engine.OONIRunRevisions
import org.openobservatory.ooniprobe.R
import org.openobservatory.ooniprobe.databinding.FragmentRevisionsBinding
import org.openobservatory.ooniprobe.databinding.ItemTextBinding


class RevisionsFragment : Fragment() {

companion object {

const val ARG_PREVIOUS_REVISIONS = "previous-revisions"
const val ARG_OONI_RUN_LINK_ID = "oonirun-link-id"

/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param previousRevisions Previous revisions in JSON format.
* @return A new instance of fragment RevisionsFragment.
*/
@JvmStatic
fun newInstance(runId: Long, previousRevisions: String) =
RevisionsFragment().apply {
arguments = Bundle().apply {
putString(ARG_PREVIOUS_REVISIONS, previousRevisions)
putLong(ARG_OONI_RUN_LINK_ID, runId)
}
}
}

private var revisions: OONIRunRevisions? = null
private var runId: Long = 0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

arguments?.let {
revisions =
Gson().fromJson(it.getString(ARG_PREVIOUS_REVISIONS), OONIRunRevisions::class.java)
runId = it.getLong(ARG_OONI_RUN_LINK_ID)
}
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentRevisionsBinding.inflate(inflater, container, false)

with(binding.list) {
layoutManager = LinearLayoutManager(context)
adapter = revisions?.revisions?.let {
RevisionsRecyclerViewAdapter(it, object : OnItemClickListener {
override fun onItemClick(position: Int) {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(
"https://run.test.ooni.org/revisions/%s?revision=%s".format(
runId,
it[position]
)
)
)
)
}
})
}
}

return binding.root
}

}

/**
* Interface for handling item clicks in the RecyclerView.
*/
interface OnItemClickListener {
/**
* Called when an item in the RecyclerView is clicked.
*
* @param position The position of the clicked item.
*/
fun onItemClick(position: Int)
}

/**
* RecyclerView adapter for displaying a list of revisions.
*
* @param values The list of revisions to display.
* @param onClickListener The click listener for handling item clicks.
*/
class RevisionsRecyclerViewAdapter(
private val values: List<String>,
private val onClickListener: OnItemClickListener,
) : RecyclerView.Adapter<RevisionsRecyclerViewAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemTextBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
holder.binding.root.setPadding(0, 10, 0, 10)
holder.binding.textView.apply {
text = "#$item"
setTextColor(
ContextCompat.getColor(holder.binding.root.context, R.color.color_blue6)
)
paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG
setOnClickListener { onClickListener.onItemClick(position) }
}
}

override fun getItemCount(): Int = values.size

inner class ViewHolder(var binding: ItemTextBinding) : RecyclerView.ViewHolder(binding.root)
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ class ReviewDescriptorUpdatesActivity : AbstractActivity() {
if ((currPos + 1) != binding.viewpager.adapter?.itemCount) {
binding.viewpager.currentItem = currPos + 1
} else {
setResult(RESULT_OK, Intent().putExtra(RESULT_MESSAGE, "Link(s) updated"))
setResult(
RESULT_OK,
Intent().putExtra(RESULT_MESSAGE, "Link(s) updated")
)
finish()
}
true
Expand Down Expand Up @@ -201,24 +204,10 @@ private const val DESCRIPTOR = "descriptor"
*/
class DescriptorUpdateFragment : Fragment() {

private lateinit var binding: FragmentDescriptorUpdateBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDescriptorUpdateBinding.inflate(inflater, container, false)
return binding.root
}
companion object {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
arguments?.takeIf { it.containsKey(DESCRIPTOR) }?.apply {
val descriptor: TestDescriptor =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializable(DESCRIPTOR, TestDescriptor::class.java)!!
} else {
getSerializable(DESCRIPTOR) as TestDescriptor
}
@JvmStatic
fun bindData(context: Context, descriptor: TestDescriptor, binding: FragmentDescriptorUpdateBinding): InstalledDescriptor {
val absDescriptor = InstalledDescriptor(descriptor)
binding.apply {
title.text = absDescriptor.title
Expand All @@ -229,7 +218,7 @@ class DescriptorUpdateFragment : Fragment() {
).format(descriptor.dateCreated)
}"
description.text = absDescriptor.description // Use markdown
icon.setImageResource(absDescriptor.getDisplayIcon(requireContext()))
icon.setImageResource(absDescriptor.getDisplayIcon(context))
val adapter =
ReviewDescriptorExpandableListAdapter(nettests = absDescriptor.nettests)
expandableListView.setAdapter(adapter)
Expand All @@ -238,6 +227,30 @@ class DescriptorUpdateFragment : Fragment() {
expandableListView.expandGroup(i)
}
}
return absDescriptor
}
}

private lateinit var binding: FragmentDescriptorUpdateBinding

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDescriptorUpdateBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
arguments?.takeIf { it.containsKey(DESCRIPTOR) }?.apply {
val descriptor: TestDescriptor =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializable(DESCRIPTOR, TestDescriptor::class.java)!!
} else {
getSerializable(DESCRIPTOR) as TestDescriptor
}
bindData(requireContext(), descriptor, binding)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package org.openobservatory.ooniprobe.common

import android.content.Context
import com.google.gson.Gson
import com.raizlabs.android.dbflow.sql.language.SQLite
import org.openobservatory.engine.BaseNettest
import org.openobservatory.engine.LoggerArray
import org.openobservatory.engine.OONIRunDescriptor
import org.openobservatory.engine.OONIRunRevisions
import org.openobservatory.ooniprobe.BuildConfig
import org.openobservatory.ooniprobe.activity.adddescriptor.adapter.GroupedItem
import org.openobservatory.ooniprobe.model.database.InstalledDescriptor
Expand Down Expand Up @@ -60,26 +62,24 @@ class TestDescriptorManager @Inject constructor(
val ooniContext = session.newContextWithTimeout(300)

val response: OONIRunDescriptor =
session.ooniRunFetch(ooniContext, BuildConfig.OONI_API_BASE_URL, runId)
return TestDescriptor(
runId = runId,
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
)
session.getLatestOONIRunLink(ooniContext, BuildConfig.OONI_API_BASE_URL, runId)

var revisions: OONIRunRevisions? = null

try {
if (Integer.parseInt(response.revision) > 1) {
revisions = session.getOONIRunLinkRevisions(
ooniContext,
BuildConfig.OONI_API_BASE_URL,
runId
)
}
} catch (e: Exception) {
ThirdPartyServices.logException(e)
}
return response.toTestDescriptor().apply {
previousRevision = Gson().toJson(revisions)
}
}

fun addDescriptor(
Expand Down Expand Up @@ -151,3 +151,25 @@ class TestDescriptorManager @Inject constructor(
.queryList()
}
}

fun OONIRunDescriptor.toTestDescriptor(): TestDescriptor {
return TestDescriptor(
runId = oonirunLinkId.toLong(),
name = name,
shortDescription = shortDescription,
description = description,
author = author,
nettests = nettests,
nameIntl = nameIntl,
shortDescriptionIntl = shortDescriptionIntl,
descriptionIntl = descriptionIntl,
icon = icon,
color = color,
animation = animation,
expirationDate = expirationDate,
dateCreated = dateCreated,
dateUpdated = dateUpdated,
revision = revision,
isExpired = isExpired
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ class TestDescriptor(
@Column
var revision: String? = null,


@Column(name = "previous_revision")
var previousRevision: 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,

Expand Down Expand Up @@ -241,6 +241,8 @@ class ITestDescriptor(

val revision: String? = null,

var previousRevision: String? = null,

val isExpired: Boolean? = false,

var isAutoUpdate: Boolean = false,
Expand All @@ -264,6 +266,7 @@ class ITestDescriptor(
dateCreated = dateCreated,
dateUpdated = dateUpdated,
revision = revision,
previousRevision = previousRevision,
isExpired = isExpired,
isAutoUpdate = isAutoUpdate
)
Expand Down
Loading

0 comments on commit 13ddead

Please sign in to comment.