Skip to content

Commit

Permalink
Merge pull request #599 from ooni/oonirun/add-autorun-and-auto-update…
Browse files Browse the repository at this point in the history
…-options

Feat Run V2: Implement auto run and auto update
  • Loading branch information
aanorbel committed Aug 17, 2023
2 parents ea8c3da + f41142a commit 6f85191
Show file tree
Hide file tree
Showing 15 changed files with 712 additions and 188 deletions.
8 changes: 8 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ android {
buildFeatures {
viewBinding = true
}
dataBinding {
enabled = true
enabledForTests = true
}
namespace 'org.openobservatory.ooniprobe'
}

Expand All @@ -114,6 +118,10 @@ dependencies {
implementation 'com.google.guava:guava:30.1.1-android'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
// WorkManager dependency
implementation 'androidx.work:work-runtime:2.8.1'

// Third-party
annotationProcessor 'com.github.Raizlabs.DBFlow:dbflow-processor:4.2.4'
implementation 'com.github.Raizlabs.DBFlow:dbflow-core:4.2.4'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private void manageIntent(Intent intent) {
descriptorResponse -> {
if (descriptorResponse!=null) {
binding.progressIndicator.setVisibility(View.GONE);
loadScreen(descriptorResponse);
loadV2Screen(descriptorResponse);
} else {
binding.progressIndicator.setVisibility(View.GONE);
loadInvalidAttributes();
Expand Down Expand Up @@ -135,8 +135,10 @@ else if (Intent.ACTION_SEND.equals(intent.getAction())) {
}
}

private void loadScreen(FetchTestDescriptorResponse response) {
private void loadV2Screen(FetchTestDescriptorResponse response) {

binding.v2Options.setVisibility(View.VISIBLE);
binding.items.setPadding(0, 0, 0, getResources().getDimensionPixelSize(R.dimen.activity_ooni_run_v2_items_margin_bottom));
binding.icon.setImageResource(response.suite.getIconGradient());
binding.icon.setColorFilter(getResources().getColor(R.color.color_gray7));

Expand Down Expand Up @@ -171,6 +173,8 @@ private void loadScreen(FetchTestDescriptorResponse response) {
binding.run.setVisibility(View.VISIBLE);
binding.run.setOnClickListener(
v -> {
response.descriptor.setAutoUpdate(binding.autoUpdates.isChecked());
response.descriptor.setAutoRun(binding.autoRun.isChecked());
response.descriptor.save();
ActivityCompat.startActivity(this, OverviewActivity.newIntent(this, response.suite), null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,51 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.core.text.TextUtilsCompat;
import androidx.core.view.ViewCompat;
import androidx.databinding.BindingAdapter;
import androidx.lifecycle.ViewModelProvider;

import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;

import org.openobservatory.ooniprobe.R;
import org.openobservatory.ooniprobe.activity.overview.OverviewViewModel;
import org.openobservatory.ooniprobe.common.PreferenceManager;
import org.openobservatory.ooniprobe.common.TaskExecutor;
import org.openobservatory.ooniprobe.common.ThirdPartyServices;
import org.openobservatory.ooniprobe.databinding.ActivityOverviewBinding;
import org.openobservatory.ooniprobe.model.database.Result;
import org.openobservatory.ooniprobe.domain.TestDescriptorManager;
import org.openobservatory.ooniprobe.model.database.TestDescriptor;
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 java.util.Objects;

import javax.inject.Inject;

import ru.noties.markwon.Markwon;

public class OverviewActivity extends AbstractActivity {
private static final String TEST = "test";
private static final String TAG = OverviewActivity.class.getSimpleName();

private ActivityOverviewBinding binding;
private AbstractSuite testSuite;

@Inject
PreferenceManager preferenceManager;
@Inject
OverviewViewModel viewModel;

public static Intent newIntent(Context context, AbstractSuite testSuite) {
return new Intent(context, OverviewActivity.class).putExtra(TEST, testSuite);
Expand All @@ -48,44 +62,123 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) {
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTitle(testSuite.getTitle());
binding.icon.setImageResource(testSuite.getIcon());
binding.customUrl.setVisibility(testSuite.getName().equals(WebsitesSuite.NAME) ? View.VISIBLE : View.GONE);
binding.setViewmodel(viewModel);
binding.setLifecycleOwner(this);
onTestSuiteChanged();
if(testSuite.isTestEmpty(preferenceManager)){
binding.run.setAlpha(0.5F);
binding.run.setEnabled(false);
}
if (testSuite.getName().equals(ExperimentalSuite.NAME)) {
Markwon.setMarkdown(binding.desc, testSuite.getDesc1());
if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_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);
binding.swipeRefresh.setOnRefreshListener(this::initiateRefresh);
} else {
binding.swipeRefresh.setEnabled(false);
}
Result lastResult = Result.getLastResult(testSuite.getName());
if (lastResult == null)
binding.lastTime.setText(R.string.Dashboard_Overview_LastRun_Never);
else
binding.lastTime.setText(DateUtils.getRelativeTimeSpanString(lastResult.start_time.getTime()));

setUpOnCLickListeners();
}

private void onTestSuiteChanged() {
setTitle(testSuite.getTitle());
viewModel.onTestSuiteChanged(testSuite);
binding.executePendingBindings();
}


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);
binding.runtime.setText(getString(R.string.twoParam, getString(testSuite.getDataUsage()), getString(R.string.Dashboard_Card_Seconds, testSuite.getRuntime(preferenceManager).toString())));

private void initiateRefresh() {
Log.i(TAG, "initiateRefresh");
TestDescriptor descriptorToUpdate = ((OONIRunSuite)testSuite).getDescriptor();

TaskExecutor executor = new TaskExecutor();
executor.executeTask(
() -> TestDescriptorManager.fetchDescriptorFromRunId(
descriptorToUpdate.getRunId(),
OverviewActivity.this
),
descriptor -> {
if (descriptorToUpdate.shouldUpdate(descriptor)){
if (descriptorToUpdate.isAutoUpdate()) {
updateDescriptor(descriptor, descriptorToUpdate);
} else {
prepareForUpdates(descriptor, descriptorToUpdate);
}
} else {
noUpdatesAvailable();
}
binding.swipeRefresh.setRefreshing(false);
return null;
});
}

private void updateDescriptor(TestDescriptor descriptor, TestDescriptor descriptorToUpdate) {
descriptor.setAutoUpdate(descriptorToUpdate.isAutoUpdate());
descriptor.setAutoRun(descriptorToUpdate.isAutoRun());
descriptor.save();
binding.refresh.setVisibility(View.GONE);
updateViewFromDescriptor(descriptor);
Snackbar.make(
binding.getRoot(),
"Update Successful",
BaseTransientBottomBar.LENGTH_LONG
).show();
}

private void prepareForUpdates(TestDescriptor descriptor, TestDescriptor descriptorToUpdate) {
binding.refresh.setOnClickListener(v -> updateDescriptor(descriptor, descriptorToUpdate));
binding.refresh.setVisibility(android.view.View.VISIBLE);
}

private void noUpdatesAvailable() {
Snackbar.make(
binding.getRoot(),
"No Updates available",
BaseTransientBottomBar.LENGTH_LONG
).show();
}

private void updateViewFromDescriptor(TestDescriptor descriptor) {
this.testSuite = descriptor.getTestSuite(this);
this.onTestSuiteChanged();
}

@BindingAdapter(value = {"richText", "testSuiteName"})
public static void setRichText(TextView view, String richText,String testSuiteName) {
try {
if (Objects.equals(testSuiteName,ExperimentalSuite.NAME)) {
Markwon.setMarkdown(view, richText);
if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL)
view.setTextDirection(View.TEXT_DIRECTION_RTL);
} else {
Markwon.setMarkdown(view, richText);
}
} catch (Exception e) {
e.printStackTrace();
ThirdPartyServices.logException(e);
}
}

@BindingAdapter({"resource"})
public static void setImageViewResource(ImageView imageView, int resource) {
imageView.setImageResource(resource);
}

@BindingAdapter({"dataUsage", "runTime"})
public static void setDataUsage(TextView view, int dataUsage, String runTime) {
Context context = view.getContext();
view.setText(
context.getString(
R.string.twoParam,
context.getString(dataUsage),
context.getString(R.string.Dashboard_Card_Seconds, runTime)
)
);

}

void onRunClick() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.openobservatory.ooniprobe.activity.overview;

import android.text.format.DateUtils;
import android.view.View;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import org.jetbrains.annotations.NotNull;
import org.openobservatory.ooniprobe.R;
import org.openobservatory.ooniprobe.common.PreferenceManager;
import org.openobservatory.ooniprobe.common.ThirdPartyServices;
import org.openobservatory.ooniprobe.model.database.Result;
import org.openobservatory.ooniprobe.test.suite.AbstractSuite;
import org.openobservatory.ooniprobe.test.suite.OONIRunSuite;
import org.openobservatory.ooniprobe.test.suite.WebsitesSuite;
import org.openobservatory.ooniprobe.test.test.AbstractTest;

import java.util.Objects;

import javax.inject.Inject;

public class OverviewViewModel extends ViewModel {
public MutableLiveData<AbstractSuite> suite = new MutableLiveData<>();
PreferenceManager preferenceManager;

@Inject
public OverviewViewModel(@NotNull PreferenceManager preferenceManager) {
this.preferenceManager = preferenceManager;
}

public void onTestSuiteChanged(AbstractSuite testSuite) {
suite.setValue(testSuite);
}

public int getDataUsage() {
return (suite.getValue() != null) ? suite.getValue().getDataUsage() : R.string.TestResults_NotAvailable;
}

public String getRunTime() {
try {
suite.getValue().setTestList((AbstractTest[]) null);
suite.getValue().getTestList(preferenceManager);
return suite.getValue().getRuntime(preferenceManager).toString();
} catch (Exception e) {
ThirdPartyServices.logException(e);
return null;
}
}

public String getName() {
return (suite.getValue() != null) ? suite.getValue().getName() : null;
}

public String getLastTime() {
Result lastResult = Result.getLastResult(getName());
return lastResult != null ? DateUtils.getRelativeTimeSpanString(lastResult.start_time.getTime()).toString() : null;
}

public String getAuthor() {
return suite.getValue() instanceof OONIRunSuite ? String.format("Author : %s", ((OONIRunSuite) suite.getValue()).getDescriptor().getAuthor()) : null;
}

public int getAuthorVisibility() {
return (getAuthor() != null) ? View.VISIBLE : View.GONE;
}

public int getCustomUrlVisibility() {
return Objects.equals(getName(), WebsitesSuite.NAME) ? View.VISIBLE : View.GONE;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.openobservatory.ooniprobe.common;

import static androidx.work.WorkManager.getInstance;

import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
Expand All @@ -9,6 +11,12 @@
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;

import com.google.gson.Gson;
import com.raizlabs.android.dbflow.config.FlowLog;
Expand All @@ -18,6 +26,7 @@
import org.openobservatory.ooniprobe.BuildConfig;
import org.openobservatory.ooniprobe.client.OONIAPIClient;
import org.openobservatory.ooniprobe.common.service.RunTestService;
import org.openobservatory.ooniprobe.common.worker.UpdateDescriptorsWorker;
import org.openobservatory.ooniprobe.di.ActivityComponent;
import org.openobservatory.ooniprobe.di.AppComponent;
import org.openobservatory.ooniprobe.di.ApplicationModule;
Expand All @@ -29,6 +38,7 @@
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

Expand Down Expand Up @@ -66,6 +76,27 @@ public class Application extends android.app.Application {
ThirdPartyServices.reloadConsents(Application.this);
LocaleUtils.setLocale(new Locale(_preferenceManager.getSettingsLanguage()));
LocaleUtils.updateConfig(this, getBaseContext().getResources().getConfiguration());
scheduleWorkers();
}

private void scheduleWorkers() {
getInstance(this)
.enqueueUniquePeriodicWork(
UpdateDescriptorsWorker.UPDATED_DESCRIPTORS_WORK_NAME,
ExistingPeriodicWorkPolicy.KEEP,
new PeriodicWorkRequest.Builder(UpdateDescriptorsWorker.class, 24, TimeUnit.HOURS)
.setConstraints(
new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
).build()
);
/*getInstance(this)
.beginUniqueWork(
UpdateDescriptorsWorker.UPDATED_DESCRIPTORS_WORK_NAME,
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequest.from(UpdateDescriptorsWorker.class)
).enqueue();*/
}

protected AppComponent buildDagger() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ typealias Task<R> = Callable<R>

typealias OnTaskProgressUpdate<P> = (P) -> Unit

typealias OnTaskComplete<R> = (R) -> Void
typealias OnTaskComplete<R> = (R) -> Unit

class TaskExecutor {
private val executor = Executors.newSingleThreadExecutor()
Expand Down
Loading

0 comments on commit 6f85191

Please sign in to comment.