feat: boot receiver

This commit is contained in:
arm64v8a 2023-04-27 21:18:04 +09:00
parent 4ea5cc3d40
commit c56f9b1d9f
9 changed files with 83 additions and 13 deletions

View File

@ -297,6 +297,18 @@
android:authorities="${applicationId}.androidx-startup" android:authorities="${applicationId}.androidx-startup"
tools:node="remove" /> tools:node="remove" />
<receiver
android:name="io.nekohasekai.sagernet.BootReceiver"
android:enabled="false"
android:exported="true"
android:process=":bg">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<service <service
android:name="androidx.room.MultiInstanceInvalidationService" android:name="androidx.room.MultiInstanceInvalidationService"
android:process=":bg" /> android:process=":bg" />

View File

@ -0,0 +1,42 @@
package io.nekohasekai.sagernet
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import io.nekohasekai.sagernet.bg.SubscriptionUpdater
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.ktx.app
import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher
class BootReceiver : BroadcastReceiver() {
companion object {
private val componentName by lazy { ComponentName(app, BootReceiver::class.java) }
var enabled: Boolean
get() = app.packageManager.getComponentEnabledSetting(componentName) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
set(value) = app.packageManager.setComponentEnabledSetting(
componentName, if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
)
}
override fun onReceive(context: Context, intent: Intent) {
runOnDefaultDispatcher {
SubscriptionUpdater.reconfigureUpdater()
}
if (!DataStore.persistAcrossReboot) { // sanity check
enabled = false
return
}
val doStart = when (intent.action) {
Intent.ACTION_LOCKED_BOOT_COMPLETED -> false // DataStore.directBootAware
else -> Build.VERSION.SDK_INT < 24 || SagerNet.user.isUserUnlocked
} && DataStore.selectedProxy > 0
if (doStart) SagerNet.startService()
}
}

View File

@ -7,6 +7,8 @@ object Key {
const val DB_PUBLIC = "configuration.db" const val DB_PUBLIC = "configuration.db"
const val DB_PROFILE = "sager_net.db" const val DB_PROFILE = "sager_net.db"
const val PERSIST_ACROSS_REBOOT = "isAutoConnect"
const val APP_EXPERT = "isExpert" const val APP_EXPERT = "isExpert"
const val APP_THEME = "appTheme" const val APP_THEME = "appTheme"
const val NIGHT_THEME = "nightTheme" const val NIGHT_THEME = "nightTheme"

View File

@ -7,6 +7,7 @@ import android.content.IntentFilter
import android.os.* import android.os.*
import android.widget.Toast import android.widget.Toast
import io.nekohasekai.sagernet.Action import io.nekohasekai.sagernet.Action
import io.nekohasekai.sagernet.BootReceiver
import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.SagerNet
import io.nekohasekai.sagernet.aidl.ISagerNetService import io.nekohasekai.sagernet.aidl.ISagerNetService
@ -314,6 +315,7 @@ class BaseService {
val proxy = ProxyInstance(profile, this) val proxy = ProxyInstance(profile, this)
data.proxy = proxy data.proxy = proxy
BootReceiver.enabled = DataStore.persistAcrossReboot
if (!data.closeReceiverRegistered) { if (!data.closeReceiverRegistered) {
registerReceiver(data.receiver, IntentFilter().apply { registerReceiver(data.receiver, IntentFilter().apply {
addAction(Action.RELOAD) addAction(Action.RELOAD)

View File

@ -60,7 +60,7 @@ class TileService : BaseTileService(), SagerConnection.Callback {
} }
override fun onClick() { override fun onClick() {
toggle() if (isLocked) unlockAndRun(this::toggle) else toggle()
} }
private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) { private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) {
@ -72,15 +72,18 @@ class TileService : BaseTileService(), SagerConnection.Callback {
icon = iconBusy icon = iconBusy
state = Tile.STATE_ACTIVE state = Tile.STATE_ACTIVE
} }
BaseService.State.Connected -> { BaseService.State.Connected -> {
icon = iconConnected icon = iconConnected
label = profileName() label = profileName()
state = Tile.STATE_ACTIVE state = Tile.STATE_ACTIVE
} }
BaseService.State.Stopping -> { BaseService.State.Stopping -> {
icon = iconBusy icon = iconBusy
state = Tile.STATE_UNAVAILABLE state = Tile.STATE_UNAVAILABLE
} }
BaseService.State.Stopped -> { BaseService.State.Stopped -> {
icon = iconIdle icon = iconIdle
state = Tile.STATE_INACTIVE state = Tile.STATE_INACTIVE

View File

@ -151,6 +151,8 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var individual by configurationStore.string(Key.INDIVIDUAL) var individual by configurationStore.string(Key.INDIVIDUAL)
var showDirectSpeed by configurationStore.boolean(Key.SHOW_DIRECT_SPEED) { true } var showDirectSpeed by configurationStore.boolean(Key.SHOW_DIRECT_SPEED) { true }
val persistAcrossReboot by configurationStore.boolean(Key.PERSIST_ACROSS_REBOOT) { false }
var appendHttpProxy by configurationStore.boolean(Key.APPEND_HTTP_PROXY) var appendHttpProxy by configurationStore.boolean(Key.APPEND_HTTP_PROXY)
var requireTransproxy by configurationStore.boolean(Key.REQUIRE_TRANSPROXY) var requireTransproxy by configurationStore.boolean(Key.REQUIRE_TRANSPROXY)
var transproxyMode by configurationStore.stringToInt(Key.TRANSPROXY_MODE) var transproxyMode by configurationStore.stringToInt(Key.TRANSPROXY_MODE)

View File

@ -98,6 +98,14 @@ class MainActivity : ThemedActivity(),
) )
} }
} }
refreshNavMenu(DataStore.enableClashAPI)
}
fun refreshNavMenu(clashApi: Boolean) {
if (::navigation.isInitialized) {
navigation.menu.findItem(R.id.nav_traffic)?.isVisible = clashApi
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
@ -347,17 +355,6 @@ class MainActivity : ThemedActivity(),
binding.fab.changeState(state, DataStore.serviceState, animate) binding.fab.changeState(state, DataStore.serviceState, animate)
binding.stats.changeState(state) binding.stats.changeState(state)
if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show() if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show()
when (state) {
BaseService.State.Stopped -> {
runOnDefaultDispatcher {
// refresh view
ProfileManager.postUpdate(DataStore.currentProfile)
}
}
else -> {}
}
} }
override fun snackbarInternal(text: CharSequence): Snackbar { override fun snackbarInternal(text: CharSequence): Snackbar {

View File

@ -189,6 +189,11 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
val resolveDestination = findPreference<SwitchPreference>(Key.RESOLVE_DESTINATION)!! val resolveDestination = findPreference<SwitchPreference>(Key.RESOLVE_DESTINATION)!!
val acquireWakeLock = findPreference<SwitchPreference>(Key.ACQUIRE_WAKE_LOCK)!! val acquireWakeLock = findPreference<SwitchPreference>(Key.ACQUIRE_WAKE_LOCK)!!
val enableClashAPI = findPreference<SwitchPreference>(Key.ENABLE_CLASH_API)!! val enableClashAPI = findPreference<SwitchPreference>(Key.ENABLE_CLASH_API)!!
enableClashAPI.setOnPreferenceChangeListener { _, newValue ->
(activity as MainActivity?)?.refreshNavMenu(newValue as Boolean)
needReload()
true
}
serviceMode.onPreferenceChangeListener = reloadListener serviceMode.onPreferenceChangeListener = reloadListener
mixedPort.onPreferenceChangeListener = reloadListener mixedPort.onPreferenceChangeListener = reloadListener
@ -218,7 +223,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
resolveDestination.onPreferenceChangeListener = reloadListener resolveDestination.onPreferenceChangeListener = reloadListener
tunImplementation.onPreferenceChangeListener = reloadListener tunImplementation.onPreferenceChangeListener = reloadListener
acquireWakeLock.onPreferenceChangeListener = reloadListener acquireWakeLock.onPreferenceChangeListener = reloadListener
enableClashAPI.onPreferenceChangeListener = reloadListener
} }

View File

@ -6,6 +6,12 @@
app:icon="@drawable/ic_baseline_android_24" app:icon="@drawable/ic_baseline_android_24"
app:key="nekoPlugins" app:key="nekoPlugins"
app:title="@string/neko_plugin" /> app:title="@string/neko_plugin" />
<SwitchPreference
app:defaultValue="false"
app:icon="@drawable/ic_communication_phonelink_ring"
app:key="isAutoConnect"
app:summary="@string/auto_connect_summary"
app:title="@string/auto_connect" />
<moe.matsuri.nb4a.ui.ColorPickerPreference <moe.matsuri.nb4a.ui.ColorPickerPreference
android:title="@string/theme" android:title="@string/theme"
app:icon="@drawable/ic_baseline_color_lens_24" app:icon="@drawable/ic_baseline_color_lens_24"