diff --git a/.github/workflows/firebase-app-distribution.yml b/.github/workflows/firebase-app-distribution.yml index 2090d3f74..c55cc486c 100644 --- a/.github/workflows/firebase-app-distribution.yml +++ b/.github/workflows/firebase-app-distribution.yml @@ -9,17 +9,30 @@ jobs: steps: - uses: actions/checkout@v4 - - name: set up JDK 17 + - name: Resolve PR number + uses: jwalton/gh-find-current-pr@v1 + id: findPr + with: + # Can be "open", "closed", or "all". Defaults to "open". + state: open + - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - - name: build release + - name: Build `DevFullDebug` variant + if: success() && steps.findPr.outputs.number run: ./gradlew clean assembleDevFullDebug - - name: upload artifact to Firebase App Distribution + env: + PR_NUMBER: ${{ steps.findPr.outputs.pr }} + - name: Upload artifact to Firebase App Distribution uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0 + id: uploadArtifact with: appId: ${{secrets.FIREBASE_APP_ID}} serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }} groups: testers - file: app/build/outputs/apk/devFull/debug/app-dev-full-debug.apk \ No newline at end of file + file: app/build/outputs/apk/devFull/debug/app-dev-full-debug.apk + - name: Write Summary + run: | + echo "View this release in the Firebase console: ${{ steps.uploadArtifact.outputs.FIREBASE_CONSOLE_URI }}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index fa98199de..42e3e8050 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId 'org.openobservatory.ooniprobe' minSdk libs.versions.minSdk.get().toInteger() targetSdk libs.versions.targetSdk.get().toInteger() - versionName '3.8.5.1' - versionCode 108 + versionName '3.8.6' + versionCode 110 testInstrumentationRunner "org.openobservatory.ooniprobe.TestAndroidJUnitRunner" buildConfigField 'String', 'OONI_API_BASE_URL', '"https://api.ooni.io/"' buildConfigField 'String', 'NOTIFICATION_SERVER', '"https://countly.ooni.io"' @@ -55,8 +55,8 @@ android { dev { dimension 'testing' applicationIdSuffix '.dev' - versionNameSuffix "-beta.1" - versionCode versionCodeDate() + versionNameSuffix resolveVersionSuffix('beta') + versionCode resolveVersionCode() buildConfigField 'String', 'BASE_SOFTWARE_NAME', '"ooniprobe-android-dev"' resValue "string", "APP_ID", 'org.openobservatory.ooniprobe.dev' resValue "string", "APP_NAME", "OONI Dev" @@ -65,8 +65,8 @@ android { experimental { dimension 'testing' applicationIdSuffix '.experimental' - versionNameSuffix "-experimental.1" - versionCode versionCodeDate() + versionNameSuffix resolveVersionSuffix('experimental') + versionCode resolveVersionCode() buildConfigField 'String', 'BASE_SOFTWARE_NAME', '"ooniprobe-android-experimental"' resValue "string", "APP_ID", 'org.openobservatory.ooniprobe.experimental' resValue "string", "APP_NAME", "OONI Exp" @@ -144,7 +144,7 @@ dependencies { fullImplementation libs.google.play.core // Dependency Injection - implementation libs.google.dagger + implementation libs.google.dagger.lib kapt libs.google.dagger.compiler // Logger @@ -178,10 +178,20 @@ dependencies { kaptAndroidTest libs.google.dagger.compiler } -static def versionCodeDate() { +static def resolveVersionCode() { + if(System.getenv("PR_NUMBER") != null) { + return System.getenv("PR_NUMBER").toInteger() + } return new Date().format("yyyyMMdd").toInteger() } +static def resolveVersionSuffix(String variant) { + if(System.getenv("PR_NUMBER") != null){ + return "-${variant}.${System.getenv("PR_NUMBER")}" + } + return "-${variant}.1" +} + if (!getGradle().getStartParameter().getTaskRequests() .toString().contains("Fdroid")){ apply plugin: 'com.google.gms.google-services' diff --git a/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/CircumventionTest.java b/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/CircumventionTest.java index 74490219a..dc1e14a46 100644 --- a/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/CircumventionTest.java +++ b/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/CircumventionTest.java @@ -109,41 +109,4 @@ public void testBlockedTor() { onView(withId(R.id.authorities)).check(matches(withText(formattedAuthorities))); assertMeasurementRuntimeAndNetwork(measurement, testResult.network); } - - @Test - public void testSuccessfulRiseUpVPN() { - // Arrange - Result testResult = ResultFactory.createAndSave(OONITests.CIRCUMVENTION.toOONIDescriptor(c), 3, 0); - Measurement measurement = testResult.getMeasurement("riseupvpn"); - - // Act - launchDetails(testResult.id); - onView(withText("RiseupVPN Test")).check(matches(isDisplayed())).perform(click()); - - // Assert - assertMeasurementOutcome(true); - onView(withId(R.id.bootstrap_value)).check(matches(withText(TEST_RESULTS_AVAILABLE))); - onView(withId(R.id.openvpn_value)).check(matches(withText(TEST_RESULTS_AVAILABLE))); - onView(withId(R.id.bridges_value)).check(matches(withText(TEST_RESULTS_AVAILABLE))); - assertMeasurementRuntimeAndNetwork(measurement, testResult.network); - } - - @Test - public void testBlockedRiseUpVPN() { - // Arrange - Result testResult = ResultFactory.createAndSave(OONITests.CIRCUMVENTION.toOONIDescriptor(c), 0, 3); - Measurement measurement = testResult.getMeasurement("riseupvpn"); - - // Act - launchDetails(testResult.id); - onView(withText("RiseupVPN Test")).check(matches(isDisplayed())).perform(click()); - - // Assert - assertMeasurementOutcome(false); - onView(withId(R.id.bootstrap_value)).check(matches(withText("Blocked"))); - onView(withId(R.id.openvpn_value)).check(matches(withText("1 blocked"))); - onView(withId(R.id.bridges_value)).check(matches(withText("1 blocked"))); - assertMeasurementRuntimeAndNetwork(measurement, testResult.network); - } - } diff --git a/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/PerformanceTest.java b/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/PerformanceTest.java index be966e492..07138d1a8 100644 --- a/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/PerformanceTest.java +++ b/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/PerformanceTest.java @@ -1,5 +1,18 @@ package org.openobservatory.ooniprobe.ui.resultdetails; +import static androidx.test.espresso.Espresso.onData; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.swipeLeft; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.anything; +import static org.hamcrest.Matchers.containsString; + +import androidx.test.espresso.assertion.ViewAssertions; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.schibsted.spain.barista.rule.flaky.AllowFlaky; @@ -20,19 +33,6 @@ import org.openobservatory.ooniprobe.test.test.Ndt; import org.openobservatory.ooniprobe.utils.FormattingUtils; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.swipeLeft; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; -import static androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsString; -import static org.openobservatory.ooniprobe.ui.utils.RecyclerViewMatcher.withRecyclerView; - @RunWith(AndroidJUnit4.class) public class PerformanceTest extends MeasurementAbstractTest { @@ -112,35 +112,34 @@ public void testListOfMeasurements() { launchDetails(testResult.id); // Assert - onView(withId(R.id.recyclerView)) - .perform(scrollToPosition(0)); - - onView(withRecyclerView(R.id.recyclerView) - .atPositionOnView(0, R.id.data1)) - .check(matches(withText(download + " " + downloadUnity))); - onView(withRecyclerView(R.id.recyclerView) - .atPositionOnView(0, R.id.data2)) - .check(matches(withText(upload + " " + uploadUnity))); - - onView(withId(R.id.recyclerView)) - .perform(scrollToPosition(1)); - - onView(withRecyclerView(R.id.recyclerView) - .atPositionOnView(1, R.id.data1)) + onData(anything()) + .inAdapterView(withId(R.id.recyclerView)) + .atPosition(0) + .onChildView(withId(R.id.data1)) + .check(ViewAssertions.matches(withText(download + " " + downloadUnity))); + + onData(anything()) + .inAdapterView(withId(R.id.recyclerView)) + .atPosition(0) + .onChildView(withId(R.id.data2)) + .check(matches(withText(upload + " " + uploadUnity))); + + onData(anything()) + .inAdapterView(withId(R.id.recyclerView)) + .atPosition(1) + .onChildView(withId(R.id.data1)) .check(matches(withText(videoQuality))); - onView(withId(R.id.recyclerView)) - .perform(scrollToPosition(2)); - - onView(withRecyclerView(R.id.recyclerView) - .atPositionOnView(2, R.id.data1)) + onData(anything()) + .inAdapterView(withId(R.id.recyclerView)) + .atPosition(2) + .onChildView(withId(R.id.data1)) .check(matches(withText(notDetected))); - onView(withId(R.id.recyclerView)) - .perform(scrollToPosition(3)); - - onView(withRecyclerView(R.id.recyclerView) - .atPositionOnView(3, R.id.data1)) + onData(anything()) + .inAdapterView(withId(R.id.recyclerView)) + .atPosition(3) + .onChildView(withId(R.id.data1)) .check(matches(withText(notDetected))); } @@ -174,7 +173,7 @@ public void testNDT() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(0, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(0).perform(click()); // Assert onView(withText(download + downloadUnity)).check(matches(isDisplayed())); @@ -202,7 +201,7 @@ public void testStreaming() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(1, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(1).perform(click()); // Assert onView(withText(containsString(videoQuality))).check(matches(isDisplayed())); @@ -219,7 +218,7 @@ public void testRequestLine() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(2, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(2).perform(click()); // Assert onView(withText(getResourceString(R.string.TestResults_Details_Middleboxes_HTTPInvalidRequestLine_NotFound_Hero_Title))).check(matches(isDisplayed())); @@ -234,7 +233,7 @@ public void testRequestLineDetection() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(2, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(2).perform(click()); // Assert onView(withText(getResourceString(R.string.TestResults_Details_Middleboxes_HTTPInvalidRequestLine_Found_Hero_Title))).check(matches(isDisplayed())); @@ -249,7 +248,7 @@ public void testFieldManipulation() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(3, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(3).perform(click()); // Assert onView(withText(getResourceString(R.string.TestResults_Details_Middleboxes_HTTPHeaderFieldManipulation_NotFound_Hero_Title))).check(matches(isDisplayed())); @@ -264,7 +263,7 @@ public void testFieldManipulationDetection() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(3, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(3).perform(click()); // Assert onView(withText(getResourceString(R.string.TestResults_Details_Middleboxes_HTTPHeaderFieldManipulation_Found_Hero_Title))).check(matches(isDisplayed())); diff --git a/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/WebsitesTest.java b/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/WebsitesTest.java index d2e9bb67e..aad166fb6 100644 --- a/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/WebsitesTest.java +++ b/app/src/androidTest/java/org/openobservatory/ooniprobe/ui/resultdetails/WebsitesTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; +import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.assertion.ViewAssertions.matches; @@ -28,6 +29,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.anything; import static org.hamcrest.Matchers.containsString; import static org.openobservatory.ooniprobe.ui.utils.RecyclerViewMatcher.withRecyclerView; import static org.openobservatory.ooniprobe.ui.utils.ViewMatchers.waitPartialText; @@ -65,12 +67,11 @@ public void checkListOfMeasurementsTest() { // Assert for (int i = 0; i < measurements.size(); i++) { - onView(withId(R.id.recyclerView)) - .perform(scrollToPosition(i)); - - onView(withRecyclerView(R.id.recyclerView) - .atPositionOnView(i, R.id.text)) - .check(matches(withText(containsString(measurements.get(i).getUrlString())))); + onData(anything()) + .inAdapterView(withId(R.id.recyclerView)) + .atPosition(i) + .onChildView(withId(R.id.text)) + .check(matches(withText(containsString(measurements.get(i).getUrlString())))); } } @@ -84,7 +85,7 @@ public void testSucceed() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(0, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(0).perform(click()); // Assert onView(withText(headerOutcome)).check(matches(isDisplayed())); @@ -100,7 +101,7 @@ public void testBlocked() { // Act launchDetails(testResult.id); - onView(withId(R.id.recyclerView)).perform(actionOnItemAtPosition(0, click())); + onData(anything()).inAdapterView(withId(R.id.recyclerView)).atPosition(0).perform(click()); // Assert onView(withText(headerOutcome)).check(matches(isDisplayed())); 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 153af249f..7f25e605c 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java @@ -3,6 +3,7 @@ import static org.openobservatory.ooniprobe.activity.overview.OverviewViewModel.SELECT_ALL; import static org.openobservatory.ooniprobe.activity.overview.OverviewViewModel.SELECT_NONE; import static org.openobservatory.ooniprobe.activity.overview.OverviewViewModel.SELECT_SOME; +import static org.openobservatory.ooniprobe.common.PreferenceManagerExtensionKt.resolveStatus; import android.content.Context; import android.content.Intent; @@ -83,8 +84,10 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setThemeColor(descriptor.getColor()); binding.icon.setImageResource(descriptor.getDisplayIcon(this)); binding.customUrl.setVisibility(descriptor.getName().equals(OONITests.WEBSITES.getLabel()) ? View.VISIBLE : View.GONE); - Markwon markwon = Markwon.builder(this).usePlugin(new ReadMorePlugin(getString(R.string.OONIRun_ReadMore), getString(R.string.OONIRun_ReadLess))).build(); - if (descriptor.getName().equals(OONITests.EXPERIMENTAL.name())) { + Markwon markwon = Markwon.builder(this) + .usePlugin(new ReadMorePlugin(getString(R.string.OONIRun_ReadMore), getString(R.string.OONIRun_ReadLess), 400)) + .build(); + if (Objects.equals(descriptor.getName(), OONITests.EXPERIMENTAL.name())) { markwon.setMarkdown(binding.desc, descriptor.getDescription()); if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL) { binding.desc.setTextDirection(View.TEXT_DIRECTION_RTL); @@ -133,14 +136,17 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { } }); - if (adapter.isSelectedAllItems()) { - binding.switchTests.setCheckedState(MaterialCheckBox.STATE_CHECKED); - } else if (adapter.isNotSelectedAnyGroupItem()) { - binding.switchTests.setCheckedState(MaterialCheckBox.STATE_UNCHECKED); + if (descriptor.getName().equals(OONITests.EXPERIMENTAL.getLabel())) { + binding.switchTests.setChecked(resolveStatus(preferenceManager, descriptor.getName(), descriptor.preferencePrefix(), true)); } else { - binding.switchTests.setCheckedState(MaterialCheckBox.STATE_INDETERMINATE); + if (adapter.isSelectedAllItems()) { + binding.switchTests.setCheckedState(MaterialCheckBox.STATE_CHECKED); + } else if (adapter.isNotSelectedAnyGroupItem()) { + binding.switchTests.setCheckedState(MaterialCheckBox.STATE_UNCHECKED); + } else { + binding.switchTests.setCheckedState(MaterialCheckBox.STATE_INDETERMINATE); + } } - binding.switchTests.setEnabled(descriptor.hasPreferencePrefix()); // Expand all groups for (int i = 0; i < adapter.getGroupCount(); i++) { binding.expandableListView.expandGroup(i); diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewTestsExpandableListViewAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewTestsExpandableListViewAdapter.kt index fc897f280..5640144b2 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewTestsExpandableListViewAdapter.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewTestsExpandableListViewAdapter.kt @@ -46,6 +46,7 @@ class OverviewTestsExpandableListViewAdapter( when (viewModel.descriptor.value?.name) { OONITests.EXPERIMENTAL.label -> { view.findViewById(R.id.group_name).text = groupItem.name + view.findViewById(R.id.group_icon).visibility = View.GONE } else -> { @@ -99,12 +100,21 @@ class OverviewTestsExpandableListViewAdapter( } groupCheckBox.isChecked = groupItem.selected - // Disable experimental or webconnectivity test + + /** + * Hide checkbox for experimental tests. + * Experimental tests are not configurable in the settings. + * Tests in this category are not permanent and are subject to change. + * The checkbox is hidden to prevent the user from mistakenly thinking they can be configured. + */ viewModel.descriptor.value?.run { - groupCheckBox.isEnabled = hasPreferencePrefix() + if (name == OONITests.EXPERIMENTAL.label) { + groupCheckBox.visibility = View.GONE + } else { + groupCheckBox.visibility = View.VISIBLE + } } - if (groupItem.inputs?.isNotEmpty() == true) { if (isExpanded) { groupIndicator.setImageResource(R.drawable.expand_less) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewViewModel.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewViewModel.kt index 63ad8333c..9ca5b9e15 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewViewModel.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/OverviewViewModel.kt @@ -7,6 +7,8 @@ import org.openobservatory.ooniprobe.activity.AbstractActivity import org.openobservatory.ooniprobe.activity.OverviewActivity import org.openobservatory.ooniprobe.activity.runtests.RunTestsViewModel import org.openobservatory.ooniprobe.common.AbstractDescriptor +import org.openobservatory.ooniprobe.common.OONIDescriptor +import org.openobservatory.ooniprobe.common.OONITests import org.openobservatory.ooniprobe.common.PreferenceManager import org.openobservatory.ooniprobe.common.TestDescriptorManager import org.openobservatory.ooniprobe.common.disableTest @@ -34,22 +36,50 @@ class OverviewViewModel() : ViewModel() { val selectedAllBtnStatus: MutableLiveData = MutableLiveData() init { - selectedAllBtnStatus.postValue(RunTestsViewModel.SELECT_SOME) + selectedAllBtnStatus.postValue(SELECT_SOME) } + + /** + * Set the status of the selected all button. + * This method will also update the preference of the tests based on the selected status. + * For experimental tests, a single button is used to enable/disable all tests. + * @param selectedStatus the status of the selected all button + */ fun setSelectedAllBtnStatus(selectedStatus: String) { selectedAllBtnStatus.postValue(selectedStatus) - when (selectedStatus) { - SELECT_ALL -> { - descriptor.value?.nettests?.forEach { - enableTest(it.name) + descriptor.value?.let { desc -> + + when (selectedStatus) { + SELECT_ALL -> { + when (desc.name) { + OONITests.EXPERIMENTAL.label -> { + enableTest(name = desc.name) + } + + else -> { + descriptor.value?.nettests?.forEach { + enableTest(name = it.name) + } + } + } } - } - SELECT_NONE -> { - descriptor.value?.nettests?.forEach { - disableTest(it.name) + SELECT_NONE -> { + when (desc.name) { + OONITests.EXPERIMENTAL.label -> { + disableTest(name = desc.name) + } + + else -> { + descriptor.value?.nettests?.forEach { + disableTest(name = it.name) + } + } + } } + + else -> {} } } } @@ -96,4 +126,4 @@ class OverviewViewModel() : ViewModel() { const val SELECT_SOME = "SELECT_SOME" const val SELECT_NONE = "SELECT_NONE" } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt index 563e52325..3582a2248 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt @@ -18,7 +18,11 @@ import org.openobservatory.ooniprobe.activity.runtests.adapter.RunTestsExpandabl import org.openobservatory.ooniprobe.activity.runtests.models.ChildItem import org.openobservatory.ooniprobe.activity.runtests.models.GroupItem import org.openobservatory.ooniprobe.common.AbstractDescriptor +import org.openobservatory.ooniprobe.common.OONIDescriptor +import org.openobservatory.ooniprobe.common.OONITests import org.openobservatory.ooniprobe.common.PreferenceManager +import org.openobservatory.ooniprobe.common.enableTest +import org.openobservatory.ooniprobe.common.disableTest import org.openobservatory.ooniprobe.databinding.ActivityRunTestsBinding import java.io.Serializable import javax.inject.Inject @@ -104,6 +108,7 @@ class RunTestsActivity : AbstractActivity() { private fun onMenuItemClickListener(menuItem: MenuItem): Boolean { return when (menuItem.itemId) { R.id.runButton -> { + updatePreferences() val selectedChildItems: List = getChildItemsSelectedIdList() if (selectedChildItems.isNotEmpty()) { val testSuitesToRun = getGroupItemsAtLeastOneChildEnabled().map { groupItem -> @@ -124,6 +129,31 @@ class RunTestsActivity : AbstractActivity() { } } + /** + * Update the preferences based on the selected tests. + * This method is used to update the preferences when the user has selected the tests that they want to run. + */ + private fun updatePreferences() { + for (i in 0 until adapter.groupCount) { + val group = adapter.getGroup(i) + when (group.name) { + OONITests.EXPERIMENTAL.label -> { + val testNames = OONITests.EXPERIMENTAL.nettests.map { it.name }; + when(group.nettests.filter { testNames.contains(it.name) }.map { it.selected }.all { it }) { + true -> preferenceManager.enableTest(OONITests.EXPERIMENTAL.label) + false -> preferenceManager.disableTest(OONITests.EXPERIMENTAL.label) + } + } + else -> group.nettests.forEach { nettest -> + when(nettest.selected) { + true -> preferenceManager.enableTest(nettest.name) + false -> preferenceManager.disableTest(nettest.name) + } + } + } + } + } + private fun selectAllBtnStatusObserver(selectAllBtnStatus: String?) { if (!TextUtils.isEmpty(selectAllBtnStatus)) { when (selectAllBtnStatus) { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsViewModel.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsViewModel.kt index 61ec6df97..ffb1d95f3 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsViewModel.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsViewModel.kt @@ -25,41 +25,6 @@ class RunTestsViewModel() : ViewModel() { fun setSelectedAllBtnStatus(selectedStatus: String) { selectedAllBtnStatus.postValue(selectedStatus) - when (selectedStatus) { - SELECT_ALL -> { - OONITests.INSTANT_MESSAGING.nettests.forEach { - enableTest(it.name) - } - OONITests.CIRCUMVENTION.nettests.forEach { - enableTest(it.name) - } - OONITests.PERFORMANCE.nettests.forEach { - enableTest(it.name) - } - enableTest(OONITests.EXPERIMENTAL.label) - } - - SELECT_NONE -> { - OONITests.INSTANT_MESSAGING.nettests.forEach { - disableTest(it.name) - } - OONITests.CIRCUMVENTION.nettests.forEach { - disableTest(it.name) - } - OONITests.PERFORMANCE.nettests.forEach { - disableTest(it.name) - } - disableTest(OONITests.EXPERIMENTAL.label) - } - } - } - - fun disableTest(name: String) { - preferenceManager.disableTest(name) - } - - fun enableTest(name: String) { - preferenceManager.enableTest(name) } companion object { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt index 9291be328..aa690592e 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt @@ -103,22 +103,11 @@ class RunTestsExpandableListViewAdapter( if (groupItem.selected) { if (isSelectAllChildItems(groupItem.nettests)) { groupSelectionIndicator.setImageResource(R.drawable.check_box) - // NOTE: This is the only place where OONITests.EXPERIMENTAL.label is used. - // This doesn't follow the normal rule where the component tests make up the suite. - if (groupItem.name == OONITests.EXPERIMENTAL.label) { - viewModel.enableTest(OONITests.EXPERIMENTAL.label) - } } else { groupSelectionIndicator.setImageResource(R.drawable.check_box_outline_blank) - if (groupItem.name == OONITests.EXPERIMENTAL.label) { - viewModel.disableTest(OONITests.EXPERIMENTAL.label) - } } } else { groupSelectionIndicator.setImageResource(R.drawable.check_box_outline_blank) - if (groupItem.name == OONITests.EXPERIMENTAL.label) { - viewModel.disableTest(OONITests.EXPERIMENTAL.label) - } } groupSelectionIndicator.setOnClickListener { if (groupItem.selected && isSelectAllChildItems(groupItem.nettests)) { @@ -193,7 +182,6 @@ class RunTestsExpandableListViewAdapter( setOnClickListener { if (childItem.selected) { childItem.selected = false - viewModel.disableTest(childItem.name) if (isNotSelectedAnyChildItems(groupItem.nettests)) { groupItem.selected = false } @@ -204,7 +192,6 @@ class RunTestsExpandableListViewAdapter( } } else { childItem.selected = true - viewModel.enableTest(childItem.name) groupItem.selected = true if (isSelectedAllItems(groupedListData)) { viewModel.setSelectedAllBtnStatus(SELECT_ALL) 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 95e0f53ae..21ca5f8f7 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/OONIDescriptor.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/OONIDescriptor.kt @@ -34,17 +34,24 @@ abstract class AbstractDescriptor( ) : BaseDescriptor(name = name, nettests = nettests) { /** - * Checks if any of the nettests are enabled based on the preferences stored in the provided [PreferenceManager]. + * 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 nettests.any { - preferenceManager.resolveStatus( - name = it.name, - prefix = preferencePrefix(), - ) + open fun isEnabled(preferenceManager: PreferenceManager): Boolean { + return when (name) { + OONITests.EXPERIMENTAL.label -> preferenceManager.isExperimentalOn + OONITests.WEBSITES.label -> preferenceManager.countEnabledCategory() > 0 + else -> nettests.any { + preferenceManager.resolveStatus( + name = it.name, + prefix = preferencePrefix(), + ) + } } } @@ -82,7 +89,6 @@ abstract class AbstractDescriptor( TestGroupItem( selected = when (name) { OONITests.EXPERIMENTAL.label -> preferenceManager.isExperimentalOn - OONITests.WEBSITES.label -> preferenceManager.countEnabledCategory() > 0 else -> preferenceManager.resolveStatus( name = it.name, prefix = preferencePrefix(), @@ -354,7 +360,7 @@ enum class OONITests( * [STUN Reachability](https://github.com/ooni/spec/blob/master/nettests/ts-025-stun-reachability.md) * [DNS Check](https://github.com/ooni/spec/blob/master/nettests/ts-028-dnscheck.md) - + * [RiseupVPN](https://ooni.org/nettest/riseupvpn/) * [ECH Check](https://github.com/ooni/spec/blob/master/nettests/ts-039-echcheck.md) @@ -378,6 +384,81 @@ enum class OONITests( } } +fun autoRunTests(context: Context, preferenceManager: PreferenceManager): List { + + return ooniDescriptors(context).filter { ooniDescriptor -> + when (ooniDescriptor.name) { + OONITests.EXPERIMENTAL.label -> preferenceManager.resolveStatus( + name = ooniDescriptor.name, + prefix = ooniDescriptor.preferencePrefix(), + autoRun = true + ) + + else -> ooniDescriptor.nettests.any { nettest -> + preferenceManager.resolveStatus( + name = nettest.name, + prefix = ooniDescriptor.preferencePrefix(), + autoRun = true + ) + } + } + }.map { ooniDescriptor -> + when (ooniDescriptor.name) { + OONITests.EXPERIMENTAL.label -> DynamicTestSuite( + name = ooniDescriptor.name, + title = ooniDescriptor.title, + shortDescription = ooniDescriptor.shortDescription, + description = ooniDescriptor.description, + icon = ooniDescriptor.getDisplayIcon(context), + icon_24 = ooniDescriptor.getDisplayIcon(context), + color = ooniDescriptor.color, + animation = ooniDescriptor.animation, + dataUsage = ooniDescriptor.dataUsage, + nettest = (ooniDescriptor.nettests).run { + this + (ooniDescriptor.longRunningTests?.filter { nettest -> + preferenceManager.resolveStatus( + name = nettest.name, + prefix = ooniDescriptor.preferencePrefix(), + autoRun = true + ) + } ?: listOf()) + } + ).apply { + autoRun = true + } + + else -> DynamicTestSuite( + name = ooniDescriptor.name, + title = ooniDescriptor.title, + shortDescription = ooniDescriptor.shortDescription, + description = ooniDescriptor.description, + icon = ooniDescriptor.getDisplayIcon(context), + icon_24 = ooniDescriptor.getDisplayIcon(context), + color = ooniDescriptor.color, + animation = ooniDescriptor.animation, + dataUsage = ooniDescriptor.dataUsage, + nettest = (ooniDescriptor.nettests).filter { nettest -> + preferenceManager.resolveStatus( + name = nettest.name, + prefix = ooniDescriptor.preferencePrefix(), + autoRun = true + ) + }.run { + this + (ooniDescriptor.longRunningTests?.filter { nettest -> + preferenceManager.resolveStatus( + name = nettest.name, + prefix = ooniDescriptor.preferencePrefix(), + autoRun = true + ) + } ?: listOf()) + } + ).apply { + autoRun = true + } + } + } +} + /** * Creates a list of [OONIDescriptor] representing the OONI tests. * diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManagerExtension.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManagerExtension.kt index d8bda5c91..97b728642 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManagerExtension.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/PreferenceManagerExtension.kt @@ -23,18 +23,34 @@ private fun PreferenceManager.experimentalTestList(): MutableList { fun PreferenceManager.resolveStatus( name: String, prefix: String, autoRun: Boolean = false ): Boolean { - if (name == WebConnectivity.NAME) { - return true - } else if (experimentalTestList().contains(name)) { - return isExperimentalOn + if (!autoRun) { + if (name == WebConnectivity.NAME) { + return true + } else if (experimentalTestList().contains(name)) { + return isExperimentalOn + } } + val key = getPreferenceKey(name = name, prefix = prefix, autoRun = autoRun) return if (autoRun) { sp.getBoolean( getPreferenceKey(name = name, prefix = prefix, autoRun = autoRun), resolveStatus(name = name, prefix = prefix) ) } else { - return sp.getBoolean(getPreferenceKey(name = name, prefix = prefix), false) + /** + * If the preference key does not exist, we return the default value for the test. + * This is needed because ooni provided tests will not be explicitly enabled by default. + * + * However, we want to show them as enabled in the UI. + * + * Using the **[OONIDescriptor.preferencePrefix]** as an identifier, we can determine if the test is an ooni test(prefix is blank) + * and set the default value accordingly. + * + * The prefix is blank for ooni tests because they do not have a descriptor id. + * Additionally, for backward compatibility, the preference key for ooni tests + * is a lookup from **[PreferenceManager.getPreferenceKey]** with the test name. + */ + return sp.getBoolean(getPreferenceKey(name = name, prefix = prefix), prefix.isBlank()) } } @@ -60,9 +76,11 @@ private fun PreferenceManager.setValue( prefix: String, autoRun: Boolean = false, ): Boolean { - if (name == WebConnectivity.NAME || experimentalTestList().contains(name)) { + if (experimentalTestList().contains(name) && !autoRun) { return false } + val key = getPreferenceKey(name = name, prefix = prefix, autoRun = autoRun) + return with(sp.edit()) { putBoolean(getPreferenceKey(name = name, prefix = prefix, autoRun = autoRun), value) commit() @@ -122,7 +140,7 @@ fun PreferenceManager.getPreferenceKey(name: String): String { OONITests.EXPERIMENTAL.label -> r.getString(R.string.experimental) - else -> throw IllegalArgumentException("Unknown preference for: $name") + else -> name } } @@ -161,7 +179,7 @@ fun PreferenceManager.disableTest(name: String): Boolean { * @return true if the preference was successfully set, false otherwise. */ private fun PreferenceManager.setValue(name: String, value: Boolean): Boolean { - if (name == WebConnectivity.NAME || experimentalTestList().contains(name)) { + if (experimentalTestList().contains(name)) { return false } return with(sp.edit()) { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt index 9cef24f39..13ab06ffa 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt @@ -13,9 +13,22 @@ import io.noties.markwon.AbstractMarkwonPlugin * Read more plugin based on text length. * @see ReadMorePluginSample */ -class ReadMorePlugin(private val labelMore: String, private val labelLess: String) : - AbstractMarkwonPlugin() { - private val maxLength = 150 +class ReadMorePlugin private constructor( + private val labelMore: String, + private val labelLess: String, + private val maxLength: Int +) : AbstractMarkwonPlugin() { + + constructor( + labelMore: String, + labelLess: String, + maxLength: Int? + ) : this(labelMore, labelLess, maxLength ?: 150) + + constructor( + labelMore: String, + labelLess: String, + ) : this(labelMore, labelLess, 150) override fun afterSetText(textView: TextView) { val text = textView.text @@ -76,6 +89,7 @@ class ReadMorePlugin(private val labelMore: String, private val labelLess: Strin } companion object { + private fun trim(builder: SpannableStringBuilder) { // NB! tables use `\u00a0` (non breaking space) which is not reported as white-space diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/service/ServiceUtil.java b/app/src/main/java/org/openobservatory/ooniprobe/common/service/ServiceUtil.java index 2e7f8f768..caa2157d7 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/common/service/ServiceUtil.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/service/ServiceUtil.java @@ -1,5 +1,7 @@ package org.openobservatory.ooniprobe.common.service; +import static org.openobservatory.ooniprobe.common.OONIDescriptorKt.autoRunTests; + import android.app.job.JobInfo; import android.app.job.JobScheduler; import android.content.ComponentName; @@ -75,21 +77,7 @@ public static void startRunTestServiceUnattended(Application app) { if (!d.generateAutoRunServiceSuite.shouldStart(config.isOnWiFi(), config.isCharging(), isVPNInUse)) { return; } - - AbstractSuite suite = d.generateAutoRunServiceSuite.generate(); - ArrayList testSuites = new ArrayList<>(); - testSuites.add(suite); - testSuites.addAll( - Lists.transform( - List.of(OONITests.INSTANT_MESSAGING, OONITests.CIRCUMVENTION, OONITests.PERFORMANCE, OONITests.EXPERIMENTAL), - item -> { - DynamicTestSuite testSuite = item.toOONIDescriptor(app).getTest(app); - testSuite.setAutoRun(true); - return testSuite; - } - ) - ); - ServiceUtil.startRunTestServiceCommon(app, testSuites, false, true); + ServiceUtil.startRunTestServiceCommon(app, new ArrayList<>(autoRunTests(app, d.preferenceManager)), false, true); d.generateAutoRunServiceSuite.markAsRan(); } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt index efb96c1a8..f4c760fca 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt @@ -35,9 +35,13 @@ class DashboardFragment : Fragment(), View.OnClickListener { @Inject lateinit var viewModel: DashboardViewModel + private var descriptors: ArrayList> = ArrayList() + private lateinit var binding: FragmentDashboardBinding + private lateinit var adapter: DashboardAdapter + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -61,7 +65,8 @@ class DashboardFragment : Fragment(), View.OnClickListener { lifecycle.addObserver(viewModel) viewModel.getGroupedItemList().observe(viewLifecycleOwner) { items -> binding.recycler.layoutManager = LinearLayoutManager(requireContext()) - binding.recycler.adapter = DashboardAdapter(items, this, preferenceManager) + adapter = DashboardAdapter(items, this, preferenceManager) + binding.recycler.adapter = adapter } viewModel.getItemList().observe(viewLifecycleOwner) { items -> @@ -79,6 +84,11 @@ class DashboardFragment : Fragment(), View.OnClickListener { override fun onResume() { super.onResume() + /** + * Updates the list of tests when the user changes the default configuration + * after starting a test from [RunTestsActivity] + */ + binding.recycler.post { adapter.notifyDataSetChanged() } setLastTest() if (ReachabilityManager.isVPNinUse(this.context) && preferenceManager.isWarnVPNInUse @@ -115,4 +125,4 @@ class DashboardFragment : Fragment(), View.OnClickListener { null ) } -} \ No newline at end of file +} diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 997322e49..14de69c06 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -23,8 +23,8 @@ كل مرّّة أدير فيها OONI Probe، سيتم نشر بيانات الشبكة التي أجمعها أوتوماتيكيّاً. تحذير للزّيادة من شفافيّة الرّقابة على الانترنت، يتم النشر الأوتوماتيكي لجميع بيانات الشبكة التابعة لمستخدمي OONI Probe (إلا إذا اختاروا عدم المشاركة في ذلك من خلال الإعدادات). - الاختبارات الأوتوماتيكيّة - To measure internet censorship every day, please enable automated testing so that OONI Probe can run tests periodically.\n\nDon\'t worry, we\'ll be mindful of battery usage. \n\nYou can disable automated testing from the settings at any time. + الاختبارات التلقائية + لقياس الرقابة على الانترنت كل يوم، يُرجى تفعيل الاختبار التلقائي لكي يتمكن OONI Probe من تشغيل الاختبارات بشكل دوري.\n\nلا تقلق، سوف نضع في الحسبان استهلاك طاقة البطارية.\n\nيمكنك تعطيل الاختبار التلقائي انطلاقا من الإعدادات في أي وقت. التبليغ عن التعطيلات لتحسين خدمات OONI Probe نود تجميع تقارير حول تعطيلات في عمل التطبيق بمجهوليّة.\n\nهل تودّون بالمشاركة في إرسال التبليغات عن التعطيلات لفريق مطوّري OONI؟ ‮نعم @@ -54,7 +54,7 @@ أغلِق السجل إيقاف الاختبار جار ... يرجى الانتظار حتى الانتهاء من الاختبارات الجارية حاليّاً - Proxy in use + الوسيط يعمل انقر البطاقة للمزيد ~%1$sث اختبار حجب المواقع @@ -71,8 +71,8 @@ تحققوا من حجب [Psiphon](https://ooni.io/nettest/psiphon/) و [تور](https://ooni.io/nettest/tor/).\n\nستنشر نتائج اختبارك على [متصفح OONI](https://explorer.ooni.org/) و على [OONI API](https://api.ooni.io/). إجراء الاختبارات التجريبيّة الجديدة يمكنكم إجراء بعض الاختبارات التجريبيّة الجديدة المطوّرة من قبل فريق OONI: \n%1$s\n\nسيتم نشر نتائج اختباراتكم على كل من [مكتشف OONI](https://explorer.ooni.org) و [واجهة تطبيق OONI](https://api.ooni.io/) - The following tests will only be run as part of automated testing: - Disabled Tests + سوف تشتغل الاختبارات التالية فقط كجزء من الاختبارات التلقائية : + الاختبارات المُعطَّلة غيغابت/ثا ميغابت/ثا كيلوبت/ثا @@ -164,7 +164,7 @@ فشل باستطاعتك إجراء هذا الاختبار من جديد الرجاء المحاولة مجددًا - Learn how this test works [here](%1$s). + التعرف على كيفية عمل هذا الاختبار [هنا]([رابط]). مُتاح %1$s مُتاح. محجوب على الأرجح @@ -269,19 +269,19 @@ التغذية موافقة إلغاء - No, don\'t ask again + كلا، لا تسألني مرة أخرى حذف خطأ حاول مجددا رائع ! لا، شكراً في وقت آخر - Run anyways - Disable VPN - Always Run + شغِّل في كل الأحوال + تعطيل VPN + شغِّل دائما التطبيق غير قادر على إجراء الاختبار. من فضلك تحقق من اتصال الإنترنت. التطبيق غير قادر على تحميل قائمة المواقع. من فضلك حاول مرة أخرى. - Please wait for the current running tests to finish, before starting a new test. + يُرجى انتظار الاختبارات الحالية حتى تنتهي، قبل بدء اختبار جديد. التطبيق بحاجة الى تصريح التنبيهات. من فضلك قم بإعطاء التطبيق تصريح التنبيهات من خلال إعدادات هاتفك ثم اسمح بها في تطبيق OONI Probe. اذهب إلى الإعدادات هذه الواجهة مغلقة لحين الإنتهاء من الاختبار. @@ -290,11 +290,11 @@ بعض نتائج الاختبارات الخاصة بك لم يتمّ رفعها إلى خوادم OONI. من فضلك قم برفعها اذا كنت ترغب بالمساهمة في تجميع بيانات OONI. رفع جارٍ تحميل %1$s ... - OONI Probe cannot run automatically without battery optimization. Do you want to try again? - Please disable your VPN connection. - If you run OONI Probe with a VPN enabled, the test results may appear to come from the wrong country. Please disable your VPN connection. - Some measurements were taken over VPN. - If you upload measurements taken when VPN enabled, the test results may appear to come from the wrong country. + لا يمكن لـ OONI Probe أن يعمل تلقائيا بدون تحسينات البطارية. هل ترغب في المحاولة مرة أخرى ؟ + يُرجى تعطيل اتصالك عبر VPN. + إذا قمت بتشغيل OONI Probe عبر VPN مُفعَّل، سوف يظهر مصدر نتائج القياسات من دولة غير صحيحة. يُرجى تعطيل اتصالك عبر VPN. + لقد تم أخذ بعض القياسات عبر VPN. + إذا قمت برفع القياسات عبر اتصال VPN مُفعَّل، سوف يظهر مصدر نتائج القياسات من دولة غير صحيحة. تم التحميل بنجاح عرض سجل الإخفاق احصل.ي على تحديثات حول الرّقابة على الانترنت @@ -320,9 +320,9 @@ JSON فارغ هل تود.ين إيقاف هذا الاختبار؟ هذا سيعطّل الاختبار الجاري فوراً - هل تودّون إجراء الاختبارات أوتوماتكياً؟ - By enabling automated testing, you will contribute OONI measurements on a regular basis. - Please allow the app to run in the background. + هل تودّون إجراء الاختبارات تلقائيا؟ + عند تفعيل الاختبار التلقائي، سوف تساهم بقياسات OONI بشكل منتظم. + يُرجى السماح بتشغيل التطبيق في الخلفية. ذكرني لاحقا نسخ إلى الحافظة لم يتمّ الرفع @@ -359,13 +359,13 @@ ‮مُفعّل إجراء تنبيه عند إنتهاء الاختبار شريط الأخبار - الاختبارات الأوتوماتيكيّة - إجراء الاختبارات أوتوماتيكياً + الاختبارات التلقائية + إجراء الاختبارات تلقائيا عدد الاختبارات الأوتوماتيكية : %1$s. آخر اختبار أوتوماتيكي %1$s. فقط عند الاتصال عبر الواي الفاي فقط عند الشحن - By enabling automatic testing, OONI Probe tests will run automatically multiple times per day. Your test results will automatically get published on OONI Explorer: https://explorer.ooni.org/ \n\nImportant: If you have a VPN enabled, OONI Probe will not run tests automatically. Please turn off your VPN for automated OONI Probe testing. Learn more: https://ooni.org/support/faq/#can-i-run-ooni-probe-over-a-vpn + عند تفعيل الاختبار التلقائي، سيشغل OONI Probe الاختبارات تلقائيا عدة مرات في اليوم. سوف ينشر OONI Explorer نتائج اختباراتك تلقائيا : https://explorer.ooni.org/\n\nهام : إذا كان عندك VPN مُفعَّل، فلن يشغل OONI Probe الاختبارات تلقائيا. يُرجى إيقاف اتصالك بـ VPN لتمكين OONI Probe للقيام بالاختبار التلقائي. للتعرف على المزيد : https://ooni.org/support/faq/#can-i-run-ooni-probe-over-a-vpn المشاركة نشر النتائج أوتوماتيكيّاً رفع النتائج يدويّاً @@ -376,16 +376,16 @@ هذه المعلومات (مثلا IT لايطاليا) مطلوبة لتحدبد الدولة التي تم اجراء القياسات فيها. هل أنت متأكد أنك تريد الغاء هذا الإختيار؟ قيامك بنشر النتائج سيرفع من الشفافية حول التدخل في الشبكة ويدعم مجتمع OONI. \n\nمعلومات الشبكة (أي رقم النظام التلقائي ASN) مطلوب لتحديد مزودي خدمات الانترنت. اختيارات الاختبار - What you configure through the above test settings (e.g. disabling the WhatsApp test) will apply to tests run manually, as well as to tests run automatically (when automated testing is enabled). - Long running test - Run long running tests in foreground? + سوف تُطبَّق إعدادات الاختبار أعلاه على الاختبارات التي سوف يتم تشغيلها يدويا عندما تقوم بتهيئة تلك الإعدادات، كما ستُطبَّق أيضا على الاختبارات التي يتم تشغيلها تلقائيا (عند تفعيل الاختبار التلقائي). + اشتغال الاختبار الطويل + إطلاق اشتغال الاختبارات الطويلة في الواجهة ؟ الخصوصية أرسل تقارير حول تعطل التطبيق متقدم الثيمة المعتمة أظهر سجلات التصحيح - See recent logs - Language Setting + الاطلاع على السجلات الأخيرة + إعدادات اللغة اختر اللغة دائمًا استخدم وسيط في اتصال النطاق Domain Fronting بروكسي في خلفيّة OONI @@ -420,7 +420,7 @@ لم يتم إدخال أي روابط ابدأ أضف موقعًا - Load from template + تحميل من قالب عدد المواقع المختبرة (0 يعني الكل) اختبر واتساب اختبر تيليجرام @@ -439,7 +439,7 @@ إختبار Psiphon إختبار Tor اختبار RiseupVPN - Warn when VPN is in use + نبِّه عند استخدام VPN إرسال إيميل لفريق الدعم يرجى وصف المشكلة التي تواجهونها: من فضلك أرسل رسالة إلكترونية إلى bugs@openobservatory.org تحتوى على معلومات عن اصدار البرنامج وإصدار iOS. اضغط على \"نسخ إلى الحافظة\" فى الأسفل لنسخ عنوان بريدنا الإلكتروني. diff --git a/fastlane/changelog.sh b/fastlane/changelog.sh index 47602af25..96743ee5e 100755 --- a/fastlane/changelog.sh +++ b/fastlane/changelog.sh @@ -1,2 +1,5 @@ #usage ./changelog.sh 69 "Release notes" -for i in $(ls metadata/android); do echo "$2" > metadata/android/$i/changelogs/$1.txt; done +for i in $(ls metadata/android); do + mkdir -p metadata/android/$i/changelogs + echo "$2" > metadata/android/$i/changelogs/$1.txt +done diff --git a/gradle.properties b/gradle.properties index a8c570082..71c004f05 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ android.enableJetifier=true android.nonFinalResIds=false android.nonTransitiveRClass=false android.useAndroidX=true -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx2048m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f769c8c0f..b7ca25c88 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -androidGradlePlugin = "8.1.2" +androidGradlePlugin = "8.2.2" barista = "3.9.0" countlySdk = "23.6.0" faker = "1.2.8" @@ -18,8 +18,8 @@ androidxCore = "1.5.0" androidxRunner = "1.5.2" androidxAppCompat = "1.6.1" androidxConstraintlayout = "2.1.4" -androidxLifecycleProcess = "2.5.1" -androidxPreference = "1.2.0" +androidxLifecycleProcess = "2.7.0" +androidxPreference = "1.2.1" workRuntime = "2.9.0" androidxLocalbroadcastmanager = "1.1.0" androidxLegacySupportV4 = "1.0.0" @@ -27,11 +27,11 @@ androidxJunit = "1.1.5" androidxEspressoCore = "3.5.1" # Google -googleGson = "2.8.9" +googleGson = "2.10" googleGuava = "30.1.1-android" -googleMaterial = "1.10.0" +googleMaterial = "1.11.0" googleDagger = "2.45" -googleFirebaseBon = "26.3.0" +googleFirebaseBon = "32.7.1" googlePlaycore = "1.10.3" # OONI @@ -47,11 +47,10 @@ junit = "4.13.2" dbflow = "4.2.4" retrofitCore = "2.9.0" retrofitLoggingInterceptor = "4.9.1" -butterknifeCore = "10.2.3" # Firebase Services # https://firebase.google.com/support/release-notes/android -gms-googleServices = "4.3.15" +gms-googleServices = "4.4.0" [libraries] # Dependencies of the included build-logic @@ -99,7 +98,7 @@ google-gson = { module = "com.google.code.gson:gson", version.ref = "googleGson" google-guava = { module = "com.google.guava:guava", version.ref = "googleGuava" } google-material = { module = "com.google.android.material:material", version.ref = "googleMaterial" } google-dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "googleDagger" } -google-dagger = { module = "com.google.dagger:dagger", version.ref = "googleDagger" } +google-dagger-lib = { module = "com.google.dagger:dagger", version.ref = "googleDagger" } google-firebase-messaging = { module = "com.google.firebase:firebase-messaging"} google-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "googleFirebaseBon" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 333ba2e02..da0a0d854 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Oct 25 19:25:06 WAT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/shared-test/build.gradle b/shared-test/build.gradle index 34d70a978..ed614df70 100644 --- a/shared-test/build.gradle +++ b/shared-test/build.gradle @@ -44,7 +44,7 @@ dependencies { implementation project(":app") // Dependency Injection (https://dagger.dev/) - implementation libs.google.dagger + implementation libs.google.dagger.lib kapt libs.google.dagger.compiler // Database Library (https://github.com/agrosner/DBFlow)