Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: OONI Run v2 API Bootstrap #582

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b81dc16
added ooni run v2 in experimental mode
aanorbel Jul 10, 2023
64a54cf
updated app to support OONI Run V2 links
aanorbel Jul 12, 2023
0e1a351
updated app to cache test descriptors
aanorbel Jul 13, 2023
b12636a
updated app to cache test descriptors
aanorbel Jul 13, 2023
57c3bba
updated app to use string resources in dashboard and overview instead…
aanorbel Jul 13, 2023
ba1076e
added result row
aanorbel Jul 13, 2023
6ec5283
updated result list and ooni run screen
aanorbel Jul 14, 2023
cabac91
update with change in cli interface
aanorbel Jul 15, 2023
b8dedf6
updated run activity to install link and navigate to overview activity
aanorbel Jul 18, 2023
d996736
modified list on ooni run activity to display inputs for nettests
aanorbel Jul 19, 2023
22cf159
Updates recycler view for inputs
aanorbel Jul 21, 2023
c19ac4f
Updates OONI Run V2 to install links even when tests are already runn…
aanorbel Aug 1, 2023
1158fb0
Merge branch 'master' of github.com:ooni/probe-android into xoonirun
aanorbel Aug 1, 2023
92950eb
Merge branch 'master' of github.com:ooni/probe-android into xoonirun
aanorbel Aug 2, 2023
44a3d4f
Merge branch 'xoonirun' of github.com:ooni/probe-android into xoonirun
aanorbel Aug 2, 2023
f6e3782
Adds support for icons and a default icons when the Ooni Run V2 link …
aanorbel Aug 2, 2023
e8f2307
Updated Icon color for OONI Run V2 Icons
aanorbel Aug 3, 2023
09c61e0
Merge branch 'chore/running-activity-upgrade-to-view-binding' of gith…
aanorbel Aug 3, 2023
a94376f
Updated `RunningActivity` to show Icon of OONI Run V2 link
aanorbel Aug 3, 2023
4d7c065
Merge branch 'chore/upgdate-ooni-run-activity-to-viewbinding' of gith…
aanorbel Aug 3, 2023
23d541f
Merge branch 'chore/overview-activity-upgrade-to-view-binding' of git…
aanorbel Aug 3, 2023
781c70d
Updated Result to contain reference to the descriptor.
aanorbel Aug 4, 2023
8c13a21
Added support for translation
aanorbel Aug 4, 2023
e4989af
Satisfied constructor dependency for testsuites in test package
aanorbel Aug 5, 2023
003a9c5
Add measurement count to run item
aanorbel Aug 7, 2023
221d446
Start implementation of autorun and auto update
aanorbel Aug 10, 2023
a981699
add support for item update by swipe to refresh
aanorbel Aug 10, 2023
c6d1f43
Updated logic for automatically updating and approved updated
aanorbel Aug 11, 2023
b82cc0b
Merge branch 'master' of github.com:ooni/probe-android into xoonirun
aanorbel Aug 11, 2023
ca44991
Merge branch 'xoonirun' of github.com:ooni/probe-android into oonirun…
aanorbel Aug 11, 2023
ea8c3da
Updated `OoniRunActivity` to fetch v2 descriptor async
aanorbel Aug 14, 2023
0e3dc3c
Merge branch 'xoonirun' of github.com:ooni/probe-android into oonirun…
aanorbel Aug 14, 2023
2d7d8b7
Updated xml to remove duplicate tools
aanorbel Aug 14, 2023
ebeca76
Updates UI on refresh complete
aanorbel Aug 15, 2023
f41142a
Updated ViewModel
aanorbel Aug 16, 2023
6f85191
Merge pull request #599 from ooni/oonirun/add-autorun-and-auto-update…
aanorbel Aug 17, 2023
7ab812a
- Changed theme for `OverviewActivity`, `ResultDetailActivity`, `Meas…
aanorbel Aug 18, 2023
f99de74
Adds default property values for `color` and `animation` if backend d…
aanorbel Aug 21, 2023
b9a03b3
removed default animation
aanorbel Aug 21, 2023
75d166b
Modified scheduling of workers.
aanorbel Aug 25, 2023
04eaff1
Updated scheduling of workers
aanorbel Aug 25, 2023
368b509
Merge pull request #601 from ooni/oonirun/add-support-for-color-and-a…
aanorbel Aug 28, 2023
c0d34c2
Adds ooni run id to anotations
aanorbel Oct 9, 2023
c152544
Merge pull request #620 from ooni/oonirun/add-run-id-to-anotations
aanorbel Oct 11, 2023
21e5ebb
Merge branch 'master' of github.com:ooni/probe-android into xoonirun
aanorbel Oct 31, 2023
fa156e8
Merge branches 'xoonirun' and 'master' of github.com:ooni/probe-andro…
aanorbel Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ Generate code coverage report (after all unit and instrumented tests successfull
To manage translations check out our [translation repo](https://github.com/ooni/translations)
and follow the instructions there.

## Importing Icons

```shell
./scripts/import_icons.sh ~/Downloads/react-icons-xmls
```
Comment on lines +149 to +153
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## Contributing

* Write some code
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply from: 'jacoco.gradle'
apply plugin: 'org.jetbrains.kotlin.android'

android {
compileSdk 34
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,32 @@
android:host="nettest"
android:scheme="ooni" />
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<action android:name="${applicationId}.nettest" />

<data
android:host="run.test.ooni.org"
android:pathPrefix="/v2"
android:scheme="https" />
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="runv2"
android:scheme="ooni" />
</intent-filter>

</activity>

<activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
if (Patterns.WEB_URL.matcher(sanitizedUrl).matches() && sanitizedUrl.length() < 2084)
urls.add(Url.checkExistingUrl(sanitizedUrl).toString());
}
WebsitesSuite suite = new WebsitesSuite();
WebsitesSuite suite = new WebsitesSuite(getResources());
suite.getTestList(preferenceManager)[0].setInputs(urls);

RunningActivity.runAsForegroundService(CustomWebsiteActivity.this, suite.asArray(), this::finish, preferenceManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@
import android.webkit.URLUtil;
import android.widget.Toast;

import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentTransaction;

import com.google.common.collect.Lists;
import com.google.gson.Gson;

import org.openobservatory.engine.OONIRunNettest;
import org.openobservatory.ooniprobe.BuildConfig;
import org.openobservatory.ooniprobe.R;
import org.openobservatory.ooniprobe.common.PreferenceManager;
import org.openobservatory.ooniprobe.common.ThirdPartyServices;
import org.openobservatory.ooniprobe.databinding.ActivityOonirunBinding;
import org.openobservatory.ooniprobe.domain.GetTestSuite;
import org.openobservatory.ooniprobe.domain.TestDescriptorManager;
import org.openobservatory.ooniprobe.domain.TestDescriptorManager.FetchTestDescriptorResponse;
import org.openobservatory.ooniprobe.domain.VersionCompare;
import org.openobservatory.ooniprobe.domain.models.Attribute;
import org.openobservatory.ooniprobe.item.TextItem;
import org.openobservatory.ooniprobe.fragment.OoniRunListFragment;
import org.openobservatory.ooniprobe.test.suite.AbstractSuite;

import java.util.ArrayList;
Expand All @@ -29,13 +34,9 @@

import javax.inject.Inject;

import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerAdapter;
import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem;

public class OoniRunActivity extends AbstractActivity {
ActivityOonirunBinding binding;
private ArrayList<HeterogeneousRecyclerItem> items;
private HeterogeneousRecyclerAdapter<HeterogeneousRecyclerItem> adapter;
private ArrayList<OONIRunNettest> items;

@Inject
PreferenceManager preferenceManager;
Expand All @@ -58,12 +59,7 @@ protected void onCreate(Bundle savedInstanceState) {
setSupportActionBar(binding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
binding.recycler.setLayoutManager(layoutManager);
binding.recycler.addItemDecoration(new DividerItemDecoration(this, layoutManager.getOrientation()));
items = new ArrayList<>();
adapter = new HeterogeneousRecyclerAdapter<>(this, items);
binding.recycler.setAdapter(adapter);
manageIntent(getIntent());
}

Expand All @@ -74,16 +70,33 @@ protected void onNewIntent(Intent intent) {
}

private void manageIntent(Intent intent) {
if (isTestRunning()) {
Uri uri = intent.getData();
if (uri == null) return;

String host = uri.getHost();

if ("runv2".equals(host) || "run.test.ooni.org".equals(host)) {
try {
long runId = Long.parseLong(uri.getPathSegments().get(0));
FetchTestDescriptorResponse response = TestDescriptorManager.fetchDataFromRunId(runId, this);
loadScreen(response);
} catch (Exception exception) {
exception.printStackTrace();
ThirdPartyServices.logException(exception);
loadInvalidAttributes();
}
} else if (isTestRunning()) {
Toast.makeText(this, getString(R.string.OONIRun_TestRunningError), Toast.LENGTH_LONG).show();
finish();
}
else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String mv = uri == null ? null : uri.getQueryParameter("mv");
String tn = uri == null ? null : uri.getQueryParameter("tn");
String ta = uri == null ? null : uri.getQueryParameter("ta");
loadScreen(mv, tn, ta);
} else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
if ("nettest".equals(host) || "run.ooni.io".equals(host)) {
String mv = uri.getQueryParameter("mv");
String tn = uri.getQueryParameter("tn");
String ta = uri.getQueryParameter("ta");
loadScreen(mv, tn, ta);
} else {
loadInvalidAttributes();
}
}
else if (Intent.ACTION_SEND.equals(intent.getAction())) {
String url = intent.getStringExtra(Intent.EXTRA_TEXT);
Expand All @@ -98,9 +111,52 @@ else if (Intent.ACTION_SEND.equals(intent.getAction())) {
} else {
loadInvalidAttributes();
}
} else {
loadInvalidAttributes();
}
}

private void loadScreen(FetchTestDescriptorResponse response) {

binding.icon.setImageResource(response.suite.getIconGradient());
binding.icon.setColorFilter(getResources().getColor(R.color.color_gray7));

binding.author.setText(response.descriptor.getAuthor());
binding.author.setVisibility(View.VISIBLE);

binding.shortDesc.setText(response.descriptor.getShortDescription());
binding.shortDesc.setVisibility(View.VISIBLE);

binding.title.setText(response.descriptor.getName());
binding.desc.setText(response.descriptor.getDescription());


items.addAll(
Lists.transform(
Lists.newArrayList(response.suite.getTestList(null)),
test -> new OONIRunNettest(
test.getLabelResId() == (R.string.Test_Experimental_Fullname) ? test.getName() :getString(test.getLabelResId()),
test.getInputs()
)
)
);

FragmentTransaction mTransactiont = getSupportFragmentManager().beginTransaction();

mTransactiont.replace(R.id.items, OoniRunListFragment.newInstance(), OoniRunListFragment.class.getName());
mTransactiont.commit();

binding.iconBig.setVisibility(View.GONE);
// TODO: 18/07/2023 (aanorbel) Add translation
binding.run.setText("Install");
binding.run.setOnClickListener(
v -> {
response.descriptor.save();
ActivityCompat.startActivity(this, OverviewActivity.newIntent(this, response.suite), null);
}
);
}

private void loadScreen(String mv, String tn, String ta){
String[] split = BuildConfig.VERSION_NAME.split("-");
String version_name = split[0];
Expand Down Expand Up @@ -145,15 +201,20 @@ private void loadSuite(AbstractSuite suite, List<String> urls) {
binding.desc.setText(getString(R.string.OONIRun_YouAreAboutToRun));
if (urls != null) {
for (String url : urls) {
if (URLUtil.isValidUrl(url))
items.add(new TextItem(url));
if (URLUtil.isValidUrl(url)) {
items.add(new OONIRunNettest(url,new ArrayList<>()));
}
}
adapter.notifyTypesChanged();
binding.iconBig.setVisibility(View.GONE);
} else {
binding.iconBig.setImageResource(suite.getIcon());
binding.iconBig.setVisibility(View.VISIBLE);
}
FragmentTransaction mTransactiont = getSupportFragmentManager().beginTransaction();

mTransactiont.replace(R.id.items, OoniRunListFragment.newInstance(), OoniRunListFragment.class.getName());
mTransactiont.commit();

binding.run.setOnClickListener(v -> {

RunningActivity.runAsForegroundService(OoniRunActivity.this, suite.asArray(),this::finish, preferenceManager);
Expand All @@ -170,4 +231,8 @@ private void loadInvalidAttributes() {
binding.iconBig.setVisibility(View.VISIBLE);
binding.run.setOnClickListener(v -> finish());
}

public ArrayList<OONIRunNettest> getItems() {
return items;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,31 @@
import android.os.Bundle;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.text.TextUtilsCompat;
import androidx.core.view.ViewCompat;

import org.openobservatory.ooniprobe.R;
import org.openobservatory.ooniprobe.common.PreferenceManager;
import org.openobservatory.ooniprobe.databinding.ActivityOverviewBinding;
import org.openobservatory.ooniprobe.model.database.Result;
import org.openobservatory.ooniprobe.test.suite.AbstractSuite;
import org.openobservatory.ooniprobe.test.suite.ExperimentalSuite;
import org.openobservatory.ooniprobe.test.suite.OONIRunSuite;
import org.openobservatory.ooniprobe.test.suite.WebsitesSuite;
import org.openobservatory.ooniprobe.test.test.AbstractTest;

import java.util.Locale;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import ru.noties.markwon.Markwon;

public class OverviewActivity extends AbstractActivity {
private static final String TEST = "test";
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.icon) ImageView icon;
@BindView(R.id.runtime) TextView runtime;
@BindView(R.id.lastTime) TextView lastTime;
@BindView(R.id.desc) TextView desc;
@BindView(R.id.customUrl) Button customUrl;
@BindView(R.id.run) Button run;

private ActivityOverviewBinding binding;
private AbstractSuite testSuite;

@Inject
Expand All @@ -55,56 +44,57 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) {
getActivityComponent().inject(this);
testSuite = (AbstractSuite) getIntent().getSerializableExtra(TEST);
setTheme(testSuite.getThemeLight());
setContentView(R.layout.activity_overview);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
binding = ActivityOverviewBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTitle(testSuite.getTitle());
icon.setImageResource(testSuite.getIcon());
customUrl.setVisibility(testSuite.getName().equals(WebsitesSuite.NAME) ? View.VISIBLE : View.GONE);
binding.icon.setImageResource(testSuite.getIcon());
binding.customUrl.setVisibility(testSuite.getName().equals(WebsitesSuite.NAME) ? View.VISIBLE : View.GONE);
if(testSuite.isTestEmpty(preferenceManager)){
run.setAlpha(0.5F);
run.setEnabled(false);
binding.run.setAlpha(0.5F);
binding.run.setEnabled(false);
}
if (testSuite.getName().equals(ExperimentalSuite.NAME)) {
String experimentalLinks =
"\n\n* [STUN Reachability](https://github.com/ooni/spec/blob/master/nettests/ts-025-stun-reachability.md)" +
"\n\n* [DNS Check](https://github.com/ooni/spec/blob/master/nettests/ts-028-dnscheck.md)" +
"\n\n* [Tor Snowflake](https://ooni.org/nettest/tor-snowflake/) "+ String.format(" ( %s )",getString(R.string.Settings_TestOptions_LongRunningTest))+
"\n\n* [Vanilla Tor](https://github.com/ooni/spec/blob/master/nettests/ts-016-vanilla-tor.md) " + String.format(" ( %s )",getString(R.string.Settings_TestOptions_LongRunningTest));
Markwon.setMarkdown(desc, getString(testSuite.getDesc1(), experimentalLinks));
Markwon.setMarkdown(binding.desc, testSuite.getDesc1());
if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL)
desc.setTextDirection(View.TEXT_DIRECTION_RTL);
binding.desc.setTextDirection(View.TEXT_DIRECTION_RTL);
} else {
Markwon.setMarkdown(binding.desc, testSuite.getDesc1());
}

if (testSuite.getName().equals(OONIRunSuite.NAME)) {
binding.author.setText(String.format("Author : %s",((OONIRunSuite)testSuite).getDescriptor().getAuthor()));
binding.author.setVisibility(View.VISIBLE);
}
else
Markwon.setMarkdown(desc, getString(testSuite.getDesc1()));
Result lastResult = Result.getLastResult(testSuite.getName());
if (lastResult == null)
lastTime.setText(R.string.Dashboard_Overview_LastRun_Never);
binding.lastTime.setText(R.string.Dashboard_Overview_LastRun_Never);
else
lastTime.setText(DateUtils.getRelativeTimeSpanString(lastResult.start_time.getTime()));
binding.lastTime.setText(DateUtils.getRelativeTimeSpanString(lastResult.start_time.getTime()));

setUpOnCLickListeners();
}

private void setUpOnCLickListeners() {
binding.run.setOnClickListener(view -> onRunClick());
binding.customUrl.setOnClickListener(view -> customUrlClick());
}

@Override protected void onResume() {
super.onResume();
testSuite.setTestList((AbstractTest[]) null);
testSuite.getTestList(preferenceManager);
runtime.setText(getString(R.string.twoParam, getString(testSuite.getDataUsage()), getString(R.string.Dashboard_Card_Seconds, testSuite.getRuntime(preferenceManager).toString())));
}

@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
binding.runtime.setText(getString(R.string.twoParam, getString(testSuite.getDataUsage()), getString(R.string.Dashboard_Card_Seconds, testSuite.getRuntime(preferenceManager).toString())));
}

@OnClick(R.id.run) void onRunClick() {
void onRunClick() {
if(!testSuite.isTestEmpty(preferenceManager)){
RunningActivity.runAsForegroundService(this, testSuite.asArray(), this::bindTestService, preferenceManager);
}
}

@OnClick(R.id.customUrl) void customUrlClick() {
void customUrlClick() {
startActivity(new Intent(this, CustomWebsiteActivity.class));
}
}
Loading