feat(android): add Telegram proxy intent and background-limit status checks
This commit is contained in:
parent
c5f8b40570
commit
db5a6cc696
|
|
@ -5,6 +5,7 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package org.flowseal.tgwsproxy
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
|
||||
data class AndroidSystemStatus(
|
||||
val ignoringBatteryOptimizations: Boolean,
|
||||
val backgroundRestricted: Boolean,
|
||||
) {
|
||||
val canKeepRunningReliably: Boolean
|
||||
get() = ignoringBatteryOptimizations && !backgroundRestricted
|
||||
|
||||
companion object {
|
||||
fun read(context: Context): AndroidSystemStatus {
|
||||
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
|
||||
val ignoringBatteryOptimizations = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
powerManager.isIgnoringBatteryOptimizations(context.packageName)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
val backgroundRestricted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
activityManager.isBackgroundRestricted
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
return AndroidSystemStatus(
|
||||
ignoringBatteryOptimizations = ignoringBatteryOptimizations,
|
||||
backgroundRestricted = backgroundRestricted,
|
||||
)
|
||||
}
|
||||
|
||||
fun openBatteryOptimizationSettings(context: Context) {
|
||||
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||
data = Uri.parse("package:${context.packageName}")
|
||||
}
|
||||
} else {
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
}
|
||||
|
||||
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
|
||||
fun openAppSettings(context: Context) {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package org.flowseal.tgwsproxy
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
|
@ -42,10 +44,23 @@ class MainActivity : AppCompatActivity() {
|
|||
binding.startButton.setOnClickListener { onStartClicked() }
|
||||
binding.stopButton.setOnClickListener { ProxyForegroundService.stop(this) }
|
||||
binding.saveButton.setOnClickListener { onSaveClicked(showMessage = true) }
|
||||
binding.openTelegramButton.setOnClickListener { onOpenTelegramClicked() }
|
||||
binding.disableBatteryOptimizationButton.setOnClickListener {
|
||||
AndroidSystemStatus.openBatteryOptimizationSettings(this)
|
||||
}
|
||||
binding.openAppSettingsButton.setOnClickListener {
|
||||
AndroidSystemStatus.openAppSettings(this)
|
||||
}
|
||||
|
||||
renderConfig(settingsStore.load())
|
||||
requestNotificationPermissionIfNeeded()
|
||||
observeServiceState()
|
||||
renderSystemStatus()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
renderSystemStatus()
|
||||
}
|
||||
|
||||
private fun onSaveClicked(showMessage: Boolean): NormalizedProxyConfig? {
|
||||
|
|
@ -71,6 +86,13 @@ class MainActivity : AppCompatActivity() {
|
|||
Snackbar.make(binding.root, R.string.service_start_requested, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun onOpenTelegramClicked() {
|
||||
val config = onSaveClicked(showMessage = false) ?: return
|
||||
if (!TelegramProxyIntent.open(this, config)) {
|
||||
Snackbar.make(binding.root, R.string.telegram_not_found, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderConfig(config: ProxyConfig) {
|
||||
binding.hostInput.setText(config.host)
|
||||
binding.portInput.setText(config.portText)
|
||||
|
|
@ -153,6 +175,35 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun renderSystemStatus() {
|
||||
val status = AndroidSystemStatus.read(this)
|
||||
|
||||
binding.systemStatusValue.text = getString(
|
||||
if (status.canKeepRunningReliably) {
|
||||
R.string.system_status_ready
|
||||
} else {
|
||||
R.string.system_status_attention
|
||||
},
|
||||
)
|
||||
|
||||
val lines = mutableListOf<String>()
|
||||
lines += if (status.ignoringBatteryOptimizations) {
|
||||
getString(R.string.system_check_battery_ignored)
|
||||
} else {
|
||||
getString(R.string.system_check_battery_active)
|
||||
}
|
||||
lines += if (status.backgroundRestricted) {
|
||||
getString(R.string.system_check_background_restricted)
|
||||
} else {
|
||||
getString(R.string.system_check_background_ok)
|
||||
}
|
||||
lines += getString(R.string.system_check_oem_note)
|
||||
binding.systemStatusHint.text = lines.joinToString("\n")
|
||||
|
||||
binding.disableBatteryOptimizationButton.isVisible = !status.ignoringBatteryOptimizations
|
||||
binding.openAppSettingsButton.isVisible = status.backgroundRestricted || !status.ignoringBatteryOptimizations
|
||||
}
|
||||
|
||||
private fun requestNotificationPermissionIfNeeded() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package org.flowseal.tgwsproxy
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
object TelegramProxyIntent {
|
||||
fun open(context: Context, config: NormalizedProxyConfig): Boolean {
|
||||
val uri = Uri.parse(
|
||||
"tg://socks?server=${Uri.encode(config.host)}&port=${config.port}"
|
||||
)
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
return try {
|
||||
context.startActivity(intent)
|
||||
true
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,58 @@
|
|||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardCornerRadius="20dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="18dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/system_status_label"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/systemStatusValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/system_status_attention"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/systemStatusHint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/disableBatteryOptimizationButton"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:text="@string/disable_battery_optimization_button" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/openAppSettingsButton"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/open_app_settings_button" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
@ -147,5 +199,13 @@
|
|||
android:enabled="false"
|
||||
android:text="@string/stop_button" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/openTelegramButton"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/open_telegram_button" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@
|
|||
<string name="service_hint_idle">Configure the proxy settings, then start the foreground service.</string>
|
||||
<string name="service_hint_starting">Starting embedded Python proxy for %1$s:%2$d.</string>
|
||||
<string name="service_hint_running">Foreground service active for %1$s:%2$d.</string>
|
||||
<string name="system_status_label">Android background limits</string>
|
||||
<string name="system_status_ready">Ready</string>
|
||||
<string name="system_status_attention">Needs attention</string>
|
||||
<string name="system_check_battery_ignored">Battery optimization: disabled for this app.</string>
|
||||
<string name="system_check_battery_active">Battery optimization: still enabled, Android may stop the proxy in background.</string>
|
||||
<string name="system_check_background_ok">Background restriction: not detected.</string>
|
||||
<string name="system_check_background_restricted">Background restriction: enabled, Android may block long-running work.</string>
|
||||
<string name="system_check_oem_note">Some phones also require manual vendor settings such as Autostart, Lock in recents, or Unrestricted battery mode.</string>
|
||||
<string name="host_hint">Proxy host</string>
|
||||
<string name="port_hint">Proxy port</string>
|
||||
<string name="dc_ip_hint">DC to IP mappings (one DC:IP per line)</string>
|
||||
|
|
@ -16,8 +24,12 @@
|
|||
<string name="save_button">Save Settings</string>
|
||||
<string name="start_button">Start Service</string>
|
||||
<string name="stop_button">Stop Service</string>
|
||||
<string name="open_telegram_button">Open in Telegram</string>
|
||||
<string name="disable_battery_optimization_button">Disable Battery Optimization</string>
|
||||
<string name="open_app_settings_button">Open App Settings</string>
|
||||
<string name="settings_saved">Settings saved</string>
|
||||
<string name="service_start_requested">Foreground service start requested</string>
|
||||
<string name="telegram_not_found">Telegram app was not found for tg://socks.</string>
|
||||
<string name="notification_title">TG WS Proxy</string>
|
||||
<string name="notification_channel_name">Proxy service</string>
|
||||
<string name="notification_channel_description">Keeps the Telegram proxy service alive in the foreground.</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue