add auto update subscriptions with interval

This commit is contained in:
Hadi Norouzi committed 2023-08-17 21:06:22 +03:30
1 parent 99307ab8f0
commit 062c0d8ddb
10 files changed
+257 -17

No files matched your search

+5
View File
@@ -132,4 +132,9 @@ dependencies {
implementation 'com.blacksquircle.ui:language-json:2.1.1' implementation 'com.blacksquircle.ui:language-json:2.1.1'
implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0' implementation 'io.github.g00fy2.quickie:quickie-bundled:1.6.0'
implementation 'com.google.zxing:core:3.5.1' implementation 'com.google.zxing:core:3.5.1'
def work_version = "2.8.1"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.work:work-multiprocess:$work_version"
} }
@@ -1,12 +1,20 @@
package com.v2ray.ang package com.v2ray.ang
import android.content.Context
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.Configuration
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
class AngApplication : MultiDexApplication() { class AngApplication : MultiDexApplication(), Configuration.Provider {
companion object { companion object {
const val PREF_LAST_VERSION = "pref_last_version" const val PREF_LAST_VERSION = "pref_last_version"
lateinit var application: AngApplication
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
application = this
} }
var firstRun = false var firstRun = false
@@ -25,4 +33,10 @@ class AngApplication : MultiDexApplication() {
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE) //Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
MMKV.initialize(this) MMKV.initialize(this)
} }
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg")
.build()
}
} }
@@ -87,4 +87,11 @@ object AppConfig {
const val MSG_MEASURE_CONFIG = 7 const val MSG_MEASURE_CONFIG = 7
const val MSG_MEASURE_CONFIG_SUCCESS = 71 const val MSG_MEASURE_CONFIG_SUCCESS = 71
const val MSG_MEASURE_CONFIG_CANCEL = 72 const val MSG_MEASURE_CONFIG_CANCEL = 72
// subscription settings
const val SUBSCRIPTION_AUTO_UPDATE = "pref_auto_update_subscription"
const val SUBSCRIPTION_AUTO_UPDATE_INTERVAL = "pref_auto_update_interval"
const val DEFAULT_UPDATE_INTERVAL = "1440" // 24 hours
const val UPDATE_TASK_NAME = "subscription-updater"
} }
@@ -1,8 +1,11 @@
package com.v2ray.ang.dto package com.v2ray.ang.dto
data class SubscriptionItem( data class SubscriptionItem(
var remarks: String = "", var remarks: String = "",
var url: String = "", var url: String = "",
var enabled: Boolean = true, var enabled: Boolean = true,
val addedTime: Long = System.currentTimeMillis()) { val addedTime: Long = System.currentTimeMillis(),
} var lastUpdated: Long = -1,
var autoUpdate: Boolean = false,
val updateInterval: Int? = null,
)
@@ -0,0 +1,83 @@
package com.v2ray.ang.service
import android.Manifest
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.delay
object SubscriptionUpdater {
const val notificationChannel = "update-subscription-channel"
class UpdateTask(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
private val notificationManager = NotificationManagerCompat.from(applicationContext)
private val notification =
NotificationCompat.Builder(applicationContext, notificationChannel)
.setWhen(0)
.setTicker("Update")
.setContentTitle("Subscription Update")
.setSmallIcon(R.drawable.ic_stat_name)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setPriority(NotificationCompat.PRIORITY_MIN)
@SuppressLint("MissingPermission")
override suspend fun doWork(): Result {
Log.d(AppConfig.ANG_PACKAGE, "start updating subscriptions")
val subs = MmkvManager.decodeSubscriptions().filter { it.second.autoUpdate }
for (i in subs) {
val subscription = i.second
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setChannelId(notificationChannel)
val channel =
NotificationChannel(
notificationChannel,
"Subscription Update Service",
NotificationManager.IMPORTANCE_MIN
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(3, notification.build())
Log.d(AppConfig.ANG_PACKAGE, "update: ${subscription.remarks} subscription")
val configs = Utils.getUrlContentWithCustomUserAgent(subscription.url)
importBatchConfig(configs, i.first)
notification.setContentText("Updating ${subscription.remarks}")
}
notificationManager.cancel(3)
return Result.success()
}
}
fun importBatchConfig(server: String?, subid: String = "") {
val append = subid.isNullOrEmpty()
var count = AngConfigManager.importBatchConfig(server, subid, append)
if (count <= 0) {
AngConfigManager.importBatchConfig(Utils.decode(server!!), subid, append)
}
}
}
@@ -6,10 +6,21 @@ import android.text.TextUtils
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.preference.* import androidx.preference.*
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.multiprocess.RemoteWorkManager
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.service.SubscriptionUpdater
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel import com.v2ray.ang.viewmodel.SettingsViewModel
import java.sql.Time
import java.util.concurrent.TimeUnit
import kotlin.time.toDuration
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
private val settingsViewModel: SettingsViewModel by viewModels() private val settingsViewModel: SettingsViewModel by viewModels()
@@ -31,12 +42,15 @@ class SettingsActivity : BaseActivity() {
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) } private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) } private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) } private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) } private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) } private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) } private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) } private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
private val routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) } private val routingCustom by lazy { findPreference<Preference>(AppConfig.PREF_ROUTING_CUSTOM) }
private val autoUpdateCheck by lazy { findPreference<CheckBoxPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE) }
private val autoUpdateInterval by lazy { findPreference<EditTextPreference>(AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL) }
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } // val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } // val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
@@ -51,6 +65,24 @@ class SettingsActivity : BaseActivity() {
false false
} }
autoUpdateCheck?.setOnPreferenceChangeListener { _, newValue ->
val value = newValue as Boolean
autoUpdateCheck?.isChecked = value
autoUpdateInterval?.isEnabled = value
autoUpdateInterval?.text?.toLong()?.let {
if (newValue) configureUpdateTask(it) else cancelUpdateTask()
}
true
}
autoUpdateInterval?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
autoUpdateInterval?.summary =
if (TextUtils.isEmpty(nval) or (nval.toLong() < 15)) AppConfig.DEFAULT_UPDATE_INTERVAL else nval
configureUpdateTask(nval.toLong())
false
}
// licenses.onClick { // licenses.onClick {
// val fragment = LicensesDialogFragment.Builder(act) // val fragment = LicensesDialogFragment.Builder(act)
// .setNotices(R.raw.licenses) // .setNotices(R.raw.licenses)
@@ -92,13 +124,14 @@ class SettingsActivity : BaseActivity() {
true true
} }
localDns?.setOnPreferenceChangeListener{ _, any -> localDns?.setOnPreferenceChangeListener { _, any ->
updateLocalDns(any as Boolean) updateLocalDns(any as Boolean)
true true
} }
localDnsPort?.setOnPreferenceChangeListener { _, any -> localDnsPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String val nval = any as String
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval localDnsPort?.summary =
if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval
true true
} }
vpnDns?.setOnPreferenceChangeListener { _, any -> vpnDns?.setOnPreferenceChangeListener { _, any ->
@@ -125,14 +158,25 @@ class SettingsActivity : BaseActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()) val defaultSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN")) updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "") var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "") domesticDns?.summary =
defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
localDnsPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS) localDnsPort?.summary = defaultSharedPreferences.getString(
socksPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS) AppConfig.PREF_LOCAL_DNS_PORT,
httpPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP) AppConfig.PORT_LOCAL_DNS
)
socksPort?.summary =
defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
httpPort?.summary =
defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
autoUpdateInterval?.summary = defaultSharedPreferences.getString(
AppConfig.SUBSCRIPTION_AUTO_UPDATE_INTERVAL,
AppConfig.DEFAULT_UPDATE_INTERVAL
)
if (TextUtils.isEmpty(remoteDnsString)) { if (TextUtils.isEmpty(remoteDnsString)) {
remoteDnsString = AppConfig.DNS_AGENT remoteDnsString = AppConfig.DNS_AGENT
@@ -141,7 +185,8 @@ class SettingsActivity : BaseActivity() {
domesticDns?.summary = AppConfig.DNS_DIRECT domesticDns?.summary = AppConfig.DNS_DIRECT
} }
remoteDns?.summary = remoteDnsString remoteDns?.summary = remoteDnsString
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString) vpnDns?.summary =
defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
if (TextUtils.isEmpty(localDnsPort?.summary)) { if (TextUtils.isEmpty(localDnsPort?.summary)) {
localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS
@@ -155,17 +200,24 @@ class SettingsActivity : BaseActivity() {
} }
private fun updateMode(mode: String?) { private fun updateMode(mode: String?) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()) val defaultSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
val vpn = mode == "VPN" val vpn = mode == "VPN"
perAppProxy?.isEnabled = vpn perAppProxy?.isEnabled = vpn
perAppProxy?.isChecked = PreferenceManager.getDefaultSharedPreferences(requireActivity()) perAppProxy?.isChecked =
PreferenceManager.getDefaultSharedPreferences(requireActivity())
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false) .getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn localDns?.isEnabled = vpn
fakeDns?.isEnabled = vpn fakeDns?.isEnabled = vpn
localDnsPort?.isEnabled = vpn localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn vpnDns?.isEnabled = vpn
if (vpn) { if (vpn) {
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)) updateLocalDns(
defaultSharedPreferences.getBoolean(
AppConfig.PREF_LOCAL_DNS_ENABLED,
false
)
)
} }
} }
@@ -174,6 +226,33 @@ class SettingsActivity : BaseActivity() {
localDnsPort?.isEnabled = enabled localDnsPort?.isEnabled = enabled
vpnDns?.isEnabled = !enabled vpnDns?.isEnabled = !enabled
} }
private fun configureUpdateTask(interval: Long) {
val rw = RemoteWorkManager.getInstance(AngApplication.application)
rw.cancelUniqueWork(AppConfig.UPDATE_TASK_NAME)
rw.enqueueUniquePeriodicWork(
AppConfig.UPDATE_TASK_NAME,
ExistingPeriodicWorkPolicy.UPDATE,
PeriodicWorkRequest.Builder(
SubscriptionUpdater.UpdateTask::class.java,
interval,
TimeUnit.MINUTES
)
.apply {
setInitialDelay(interval, TimeUnit.MINUTES)
}
.setConstraints(
Constraints(
NetworkType.CONNECTED,
)
).build()
)
}
private fun cancelUpdateTask() {
val rw = RemoteWorkManager.getInstance(AngApplication.application)
rw.cancelUniqueWork(AppConfig.UPDATE_TASK_NAME)
}
} }
fun onModeHelpClicked(view: View) { fun onModeHelpClicked(view: View) {
@@ -5,14 +5,22 @@ import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.multiprocess.RemoteWorkManager
import com.google.gson.Gson import com.google.gson.Gson
import com.tencent.mmkv.MMKV import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivitySubEditBinding import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.service.SubscriptionUpdater
import com.v2ray.ang.util.MmkvManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.util.concurrent.TimeUnit
class SubEditActivity : BaseActivity() { class SubEditActivity : BaseActivity() {
private lateinit var binding: ActivitySubEditBinding private lateinit var binding: ActivitySubEditBinding
@@ -46,6 +54,7 @@ class SubEditActivity : BaseActivity() {
binding.etRemarks.text = Utils.getEditable(subItem.remarks) binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url) binding.etUrl.text = Utils.getEditable(subItem.url)
binding.chkEnable.isChecked = subItem.enabled binding.chkEnable.isChecked = subItem.enabled
binding.autoUpdateCheck.isChecked = subItem.autoUpdate
return true return true
} }
@@ -76,6 +85,7 @@ class SubEditActivity : BaseActivity() {
subItem.remarks = binding.etRemarks.text.toString() subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString() subItem.url = binding.etUrl.text.toString()
subItem.enabled = binding.chkEnable.isChecked subItem.enabled = binding.chkEnable.isChecked
subItem.autoUpdate = binding.autoUpdateCheck.isChecked
if (TextUtils.isEmpty(subItem.remarks)) { if (TextUtils.isEmpty(subItem.remarks)) {
toast(R.string.sub_setting_remarks) toast(R.string.sub_setting_remarks)
@@ -130,4 +140,5 @@ class SubEditActivity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
@@ -94,6 +94,28 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="@string/sub_auto_update" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/auto_update_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="2dp"
android:paddingEnd="6dp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -183,6 +183,7 @@
<string name="sub_setting_remarks">remarks</string> <string name="sub_setting_remarks">remarks</string>
<string name="sub_setting_url">Optional URL</string> <string name="sub_setting_url">Optional URL</string>
<string name="sub_setting_enable">enable update</string> <string name="sub_setting_enable">enable update</string>
<string name="sub_auto_update">Auto update</string>
<string name="title_sub_update">Update subscription</string> <string name="title_sub_update">Update subscription</string>
<string name="title_ping_all_server">Tcping all configuration</string> <string name="title_ping_all_server">Tcping all configuration</string>
<string name="title_real_ping_all_server">Real delay all configuration</string> <string name="title_real_ping_all_server">Real delay all configuration</string>
@@ -126,6 +126,21 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Subscription">
<CheckBoxPreference
android:key="pref_auto_update_subscription"
android:summary="Update your subscriptions automatically with an interval in background"
android:title="Automatic update subscriptions" />
<EditTextPreference
android:key="pref_auto_update_interval"
android:summary="1440 min"
android:inputType="number"
android:title="Subscription Update Interval (Minutes)" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/title_ui_settings"> <PreferenceCategory android:title="@string/title_ui_settings">
<CheckBoxPreference <CheckBoxPreference