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

[GSoC] Add preference to allow/warn if using metered connections #12121

Merged
merged 5 commits into from
Aug 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package com.ichi2.anki

import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.Uri
import android.text.TextUtils
import android.webkit.JavascriptInterface
Expand All @@ -44,6 +43,7 @@ import com.ichi2.libanki.Decks
import com.ichi2.libanki.SortOrder
import com.ichi2.utils.JSONException
import com.ichi2.utils.JSONObject
import com.ichi2.utils.isActiveNetworkMetered
import timber.log.Timber

open class AnkiDroidJsAPI(private val activity: AbstractFlashcardViewer) {
Expand Down Expand Up @@ -398,14 +398,7 @@ open class AnkiDroidJsAPI(private val activity: AbstractFlashcardViewer) {

@JavascriptInterface
fun ankiIsActiveNetworkMetered(): Boolean {
return try {
val cm = AnkiDroidApp.instance.applicationContext
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
cm.isActiveNetworkMetered
} catch (e: Exception) {
Timber.w(e, "Exception obtaining metered connection - assuming metered connection")
true
}
return isActiveNetworkMetered()
}

// Know if {{tts}} is supported - issue #10443
Expand Down
46 changes: 27 additions & 19 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ import com.ichi2.themes.StyledProgressDialog
import com.ichi2.ui.BadgeDrawableBuilder
import com.ichi2.utils.*
import com.ichi2.utils.Permissions.hasStorageAccessPermission
import com.ichi2.utils.isActiveNetworkMetered
import com.ichi2.widget.WidgetStatus
import kotlinx.coroutines.Job
import net.ankiweb.rsdroid.BackendFactory
Expand Down Expand Up @@ -923,13 +924,15 @@ open class DeckPicker :
private fun automaticSync() {
val preferences = AnkiDroidApp.getSharedPrefs(baseContext)

// Check whether the option is selected, the user is signed in and last sync was AUTOMATIC_SYNC_TIME ago
// (currently 10 minutes)
val hkey = preferences.getString("hkey", "")
// Check whether the option is selected, the user is signed in, last sync was AUTOMATIC_SYNC_TIME ago
// (currently 10 minutes), and is not under a metered connection (if not allowed by preference)
Copy link
Member

Choose a reason for hiding this comment

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

I'm fine with the commint "Add preference to check if sync is on metered connection" except for his title
You're adding a permission request, that should be in the commit message.
Also, you are not adding adding a preference but taking it into account. I suggest it may be nice to indicates both fact

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the heads up. Changed the commit message to:

Add "Allow sync on metered connections" preference
If disabled, auto-sync isn't triggered on metered connections and the user is asked if they want to continue if they trigger a manual sync
If enabled, sync occurs normally

val isLoggedIn = preferences.getString("hkey", "")!!.isNotEmpty()
val lastSyncTime = preferences.getLong("lastSyncTime", 0)
if (hkey!!.isNotEmpty() && preferences.getBoolean("automaticSyncMode", false) &&
Connection.isOnline && TimeManager.time.intTimeMS() - lastSyncTime > AUTOMATIC_SYNC_MIN_INTERVAL
) {
val autoSyncIsEnabled = preferences.getBoolean("automaticSyncMode", false)
val syncIntervalPassed = TimeManager.time.intTimeMS() - lastSyncTime > AUTOMATIC_SYNC_MIN_INTERVAL
val isNotBlockedByMeteredConnection = preferences.getBoolean(getString(R.string.metered_sync_key), false) || !isActiveNetworkMetered()

if (isLoggedIn && autoSyncIsEnabled && Connection.isOnline && syncIntervalPassed && isNotBlockedByMeteredConnection) {
Timber.i("Triggering Automatic Sync")
sync()
}
Expand Down Expand Up @@ -1562,25 +1565,30 @@ open class DeckPicker :
Timber.w("User not logged in")
mPullToSyncWrapper.isRefreshing = false
showSyncErrorDialog(SyncErrorDialog.DIALOG_USER_NOT_LOGGED_IN_SYNC)
} else {
return
}
/** Nested function that makes the connection to
* the sync server and starts syncing the data */
BrayanDSO marked this conversation as resolved.
Show resolved Hide resolved
fun doSync() {
BrayanDSO marked this conversation as resolved.
Show resolved Hide resolved
val syncMedia = preferences.getBoolean("syncFetchesMedia", true)

if (!BackendFactory.defaultLegacySchema) {
handleNewSync(conflict, syncMedia)
} else {
Connection.sync(
mSyncListener,
Connection.Payload(
arrayOf(
hkey,
syncMedia,
conflict,
HostNumFactory.getInstance(baseContext)
)
)
)
val data = arrayOf(hkey, syncMedia, conflict, HostNumFactory.getInstance(baseContext))
Connection.sync(mSyncListener, Connection.Payload(data))
}
}
// Warn the user in case the connection is metered
val meteredSyncIsAllowed = preferences.getBoolean(getString(R.string.metered_sync_key), false)
if (!meteredSyncIsAllowed && isActiveNetworkMetered()) {
MaterialDialog(this).show {
message(R.string.metered_sync_warning)
positiveButton(R.string.dialog_continue) { doSync() }
negativeButton(R.string.dialog_cancel)
}
} else {
doSync()
}
}

private val mSyncListener: Connection.TaskListener = object : CancellableTaskListener {
Expand Down
29 changes: 29 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/utils/NetworkUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2022 Brayan Oliveira <brayandso.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.utils

import android.net.ConnectivityManager
import androidx.core.content.getSystemService
import com.ichi2.anki.AnkiDroidApp

fun isActiveNetworkMetered(): Boolean {
BrayanDSO marked this conversation as resolved.
Show resolved Hide resolved
return AnkiDroidApp
.instance
.applicationContext
.getSystemService<ConnectivityManager>()
?.isActiveNetworkMetered
?: true
}
1 change: 1 addition & 0 deletions AnkiDroid/src/main/res/values/03-dialogs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@
<item quantity="one">An automatic sync may be triggered in %d second</item>
<item quantity="other">An automatic sync may be triggered in %d seconds</item>
</plurals>
<string name="metered_sync_warning">Your connection is metered. Data transfer may cost money</string>
lukstbit marked this conversation as resolved.
Show resolved Hide resolved
<string name="deck_picker_new">Number of new cards to see today in this deck.</string>
<string name="deck_picker_rev">Number of cards due today in this deck.</string>
<string name="deck_picker_lrn">Number of cards in learning in this deck.</string> <!-- This description is valid fos
Expand Down
3 changes: 3 additions & 0 deletions AnkiDroid/src/main/res/values/10-preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
<string name="swipe_sensitivity" maxLength="41">Swipe sensitivity</string>
<string name="tts" maxLength="41">Text to speech</string>
<string name="tts_summ">Reads out question and answer if no sound file is included</string>
<!-- Sync -->
<string name="sync_fetch_missing_media" maxLength="41">Fetch media on sync</string>
<string name="sync_fetch_missing_media_summ">Automatically fetch missing media when syncing</string>
<string name="sync_account" maxLength="41">AnkiWeb account</string>
Expand All @@ -109,6 +110,8 @@
minutes ago.</string>
<string name="sync_status_badge" maxLength="41">Display synchronization status</string>
<string name="sync_status_badge_summ">Change the sync icon when changes can be uploaded</string>
<string name="metered_sync_title" maxLength="41">Allow sync on metered connections</string>
<string name="metered_sync_summary">If disabled, you will be warned if you try to sync on a metered connection</string>
<string name="app_theme" maxLength="41">Theme</string>
<string name="day_theme" maxLength="41">Day theme</string>
<string name="night_theme" maxLength="41">Night theme</string>
Expand Down
1 change: 1 addition & 0 deletions AnkiDroid/src/main/res/values/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<string name="automatic_sync_choice_key">automaticSyncMode</string>
<string name="force_full_sync_key">force_full_sync</string>
<string name="sync_status_badge_key">showSyncStatusBadge</string>
<string name="metered_sync_key">allowMetered</string>
<string name="custom_sync_server_key">custom_sync_server_link</string>
<!-- Appearance -->
<string name="pref_appearance_screen_key">appearance_preference_group</string>
Expand Down
25 changes: 16 additions & 9 deletions AnkiDroid/src/main/res/xml/preferences_sync.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,20 @@
android:key="@string/sync_status_badge_key"
android:summary="@string/sync_status_badge_summ"
android:title="@string/sync_status_badge" />
<Preference
android:key="@string/force_full_sync_key"
android:title="@string/force_full_sync_title"
android:summary="@string/force_full_sync_summary" />
<Preference
android:key="@string/custom_sync_server_key"
android:title="@string/custom_sync_server_title"
android:fragment="com.ichi2.anki.preferences.CustomSyncServerSettingsFragment"
search:ignore="true"/>
<SwitchPreference
android:defaultValue="false"
android:key="@string/metered_sync_key"
android:title="@string/metered_sync_title"
android:summary="@string/metered_sync_summary" />
<PreferenceCategory android:title="@string/pref_cat_advanced">
<Preference
android:key="@string/force_full_sync_key"
android:title="@string/force_full_sync_title"
android:summary="@string/force_full_sync_summary" />
<Preference
android:key="@string/custom_sync_server_key"
android:title="@string/custom_sync_server_title"
android:fragment="com.ichi2.anki.preferences.CustomSyncServerSettingsFragment"
search:ignore="true"/>
</PreferenceCategory>
</PreferenceScreen>