Commit eeef4b55 authored by Mygod's avatar Mygod

Partially migrate to ActivityResultContracts

AlertDialogFragment will be migrated after the API goes stable.
parent 01dbcec7
......@@ -24,7 +24,6 @@ import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.net.VpnService
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
......@@ -32,14 +31,10 @@ import androidx.core.content.getSystemService
import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.StartService
import com.github.shadowsocks.utils.broadcastReceiver
import timber.log.Timber
class VpnRequestActivity : AppCompatActivity() {
companion object {
private const val REQUEST_CONNECT = 1
}
private var receiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -49,23 +44,13 @@ class VpnRequestActivity : AppCompatActivity() {
return
}
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
receiver = broadcastReceiver { _, _ -> request() }
receiver = broadcastReceiver { _, _ -> connect.launch(null) }
registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT))
} else request()
} else connect.launch(null)
}
private fun request() {
val intent = VpnService.prepare(this)
if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null)
else startActivityForResult(intent, REQUEST_CONNECT)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != REQUEST_CONNECT) return super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) Core.startService() else {
Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show()
Timber.e("Failed to start VpnService from onActivityResult: $data")
}
private val connect = registerForActivityResult(StartService()) {
if (it) Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show()
finish()
}
......
/*******************************************************************************
* *
* Copyright (C) 2020 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2020 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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.github.shadowsocks.utils
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.VpnService
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore
import timber.log.Timber
private val jsonMimeTypes = arrayOf("application/*", "text/*")
class OpenJson : ActivityResultContracts.GetMultipleContents() {
override fun createIntent(context: Context, input: String) = super.createIntent(context,
jsonMimeTypes.first()).apply { putExtra(Intent.EXTRA_MIME_TYPES, jsonMimeTypes) }
}
class SaveJson : ActivityResultContracts.CreateDocument() {
override fun createIntent(context: Context, input: String) =
super.createIntent(context, "profiles.json").apply { type = "application/json" }
}
class StartService : ActivityResultContract<Void?, Boolean>() {
private var cachedIntent: Intent? = null
override fun getSynchronousResult(context: Context, input: Void?): SynchronousResult<Boolean>? {
if (DataStore.serviceMode == Key.modeVpn) VpnService.prepare(context)?.let { intent ->
cachedIntent = intent
return null
}
Core.startService()
return SynchronousResult(false)
}
override fun createIntent(context: Context, input: Void?) = cachedIntent!!.also { cachedIntent = null }
override fun parseResult(resultCode: Int, intent: Intent?) = if (resultCode == Activity.RESULT_OK) {
Core.startService()
false
} else {
Timber.e("Failed to start VpnService: $intent")
true
}
}
......@@ -133,6 +133,4 @@ fun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int {
return typedValue.resourceId
}
val Intent.datas get() = listOfNotNull(data) + (clipData?.asIterable()?.mapNotNull { it.uri } ?: emptyList())
fun Preference.remove() = parent!!.removePreference(this)
......@@ -20,11 +20,8 @@
package com.github.shadowsocks
import android.app.Activity
import android.app.backup.BackupManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.VpnService
import android.os.Bundle
import android.os.RemoteException
import android.view.*
......@@ -48,6 +45,7 @@ import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.subscription.SubscriptionFragment
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.SingleInstanceActivity
import com.github.shadowsocks.utils.StartService
import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.ServiceButton
import com.github.shadowsocks.widget.StatsBar
......@@ -61,8 +59,6 @@ import timber.log.Timber
class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPreferenceDataStoreChangeListener,
NavigationView.OnNavigationItemSelectedListener {
companion object {
private const val REQUEST_CONNECT = 1
var stateListener: ((BaseService.State) -> Unit)? = null
}
......@@ -119,15 +115,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
stateListener?.invoke(state)
}
private fun toggle() = when {
state.canStop -> Core.stopService()
DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
}
else -> Core.startService()
}
private fun toggle() = if (state.canStop) Core.stopService() else connect.launch(null)
private val connection = ShadowsocksConnection(true)
override fun onServiceConnected(service: IShadowsocksService) = changeState(try {
......@@ -141,15 +129,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
connection.connect(this, this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when {
requestCode != REQUEST_CONNECT -> super.onActivityResult(requestCode, resultCode, data)
resultCode == Activity.RESULT_OK -> Core.startService()
else -> {
snackbar().setText(R.string.vpn_permission_denied).show()
Timber.e("Failed to start VpnService from onActivityResult: $data")
}
}
private val connect = registerForActivityResult(StartService()) {
if (it) snackbar().setText(R.string.vpn_permission_denied).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
......
......@@ -22,10 +22,12 @@ package com.github.shadowsocks
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.component1
import androidx.activity.result.component2
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.github.shadowsocks.plugin.AlertDialogFragment
......@@ -36,10 +38,6 @@ import com.github.shadowsocks.utils.SingleInstanceActivity
import com.github.shadowsocks.widget.ListHolderListener
class ProfileConfigActivity : AppCompatActivity() {
companion object {
const val REQUEST_CODE_PLUGIN_HELP = 1
}
class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
setTitle(R.string.unsaved_changes_prompt)
......@@ -79,9 +77,9 @@ class ProfileConfigActivity : AppCompatActivity() {
else super.onBackPressed()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != REQUEST_CODE_PLUGIN_HELP) super.onActivityResult(requestCode, resultCode, data)
else if (resultCode == Activity.RESULT_OK) AlertDialog.Builder(this)
val pluginHelp = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
(resultCode, data) ->
if (resultCode == Activity.RESULT_OK) AlertDialog.Builder(this)
.setTitle("?")
.setMessage(data?.getCharSequenceExtra(PluginContract.EXTRA_HELP_MESSAGE))
.show()
......
......@@ -30,6 +30,9 @@ import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem
import android.view.View
import androidx.activity.result.component1
import androidx.activity.result.component2
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.whenCreated
......@@ -50,7 +53,6 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
companion object PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {
override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0)
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
const val REQUEST_UNSAVED_CHANGES = 2
private const val REQUEST_PICK_PLUGIN = 3
}
......@@ -192,15 +194,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
Key.pluginConfigure -> {
val intent = PluginManager.buildIntent(plugin.selectedEntry!!.id, PluginContract.ACTION_CONFIGURE)
if (intent.resolveActivity(requireContext().packageManager) == null) showPluginEditor() else {
startActivityForResult(intent
.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.getOptions().toString()),
REQUEST_CODE_PLUGIN_CONFIGURE)
configurePlugin.launch(intent
.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.getOptions().toString()))
}
}
else -> super.onDisplayPreferenceDialog(preference)
}
}
private val configurePlugin = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
(resultCode, data) ->
when (resultCode) {
Activity.RESULT_OK -> {
val options = data?.getStringExtra(PluginContract.EXTRA_OPTIONS)
pluginConfigure.text = options
onPreferenceChange(null, options)
}
PluginContract.RESULT_FALLBACK -> showPluginEditor()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_PICK_PLUGIN -> if (resultCode == Activity.RESULT_OK) {
......@@ -219,14 +232,6 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
Snackbar.make(requireView(), R.string.plugin_untrusted, Snackbar.LENGTH_LONG).show()
}
}
REQUEST_CODE_PLUGIN_CONFIGURE -> when (resultCode) {
Activity.RESULT_OK -> {
val options = data?.getStringExtra(PluginContract.EXTRA_OPTIONS)
pluginConfigure.text = options
onPreferenceChange(null, options)
}
PluginContract.RESULT_FALLBACK -> showPluginEditor()
}
REQUEST_UNSAVED_CHANGES -> when (resultCode) {
DialogInterface.BUTTON_POSITIVE -> saveAndExit()
DialogInterface.BUTTON_NEGATIVE -> requireActivity().finish()
......
......@@ -21,11 +21,11 @@
package com.github.shadowsocks
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.text.format.Formatter
import android.util.LongSparseArray
......@@ -34,6 +34,7 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.Toolbar
import androidx.appcompat.widget.TooltipCompat
......@@ -49,7 +50,8 @@ import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.plugin.showAllowingStateLoss
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.OpenJson
import com.github.shadowsocks.utils.SaveJson
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.MainListListener
......@@ -75,9 +77,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
var instance: ProfilesFragment? = null
private const val KEY_URL = "com.github.shadowsocks.QRCodeDialog.KEY_URL"
private const val REQUEST_IMPORT_PROFILES = 1
private const val REQUEST_REPLACE_PROFILES = 3
private const val REQUEST_EXPORT_PROFILES = 2
private val iso88591 = StandardCharsets.ISO_8859_1.newEncoder()
}
......@@ -516,19 +515,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
true
}
R.id.action_import_file -> {
startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
}, REQUEST_IMPORT_PROFILES)
startFilesForResult(importProfiles)
true
}
R.id.action_replace_file -> {
startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
}, REQUEST_REPLACE_PROFILES)
startFilesForResult(replaceProfiles)
true
}
R.id.action_manual_settings -> {
......@@ -544,61 +535,46 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
true
}
R.id.action_export_file -> {
startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = "application/json"
putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited
}, REQUEST_EXPORT_PROFILES)
startFilesForResult(exportProfiles)
true
}
else -> false
}
}
private fun startFilesForResult(intent: Intent, requestCode: Int) {
private fun startFilesForResult(launcher: ActivityResultLauncher<String>) {
try {
startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
return
return launcher.launch("")
} catch (_: ActivityNotFoundException) {
} catch (_: SecurityException) {
}
(activity as MainActivity).snackbar(getString(R.string.file_manager_missing)).show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode != Activity.RESULT_OK) super.onActivityResult(requestCode, resultCode, data)
else when (requestCode) {
REQUEST_IMPORT_PROFILES -> {
val activity = activity as MainActivity
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it)
}.filterNotNull())
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
}
}
REQUEST_REPLACE_PROFILES -> {
val activity = activity as MainActivity
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it)
}.filterNotNull(), true)
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
}
}
REQUEST_EXPORT_PROFILES -> {
val profiles = ProfileManager.serializeToJson()
if (profiles != null) try {
requireContext().contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use {
it.write(profiles.toString(2))
}
} catch (e: Exception) {
Timber.w(e)
(activity as MainActivity).snackbar(e.readableMessage).show()
private fun importOrReplaceProfiles(dataUris: List<Uri>, replace: Boolean = false) {
if (dataUris.isEmpty()) return
val activity = activity as MainActivity
try {
ProfileManager.createProfilesFromJson(dataUris.asSequence().map {
activity.contentResolver.openInputStream(it)
}.filterNotNull(), replace)
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
}
}
private val importProfiles = registerForActivityResult(OpenJson()) { importOrReplaceProfiles(it) }
private val replaceProfiles = registerForActivityResult(OpenJson()) { importOrReplaceProfiles(it, true) }
private val exportProfiles = registerForActivityResult(SaveJson()) { data ->
if (data != null) ProfileManager.serializeToJson()?.let { profiles ->
val activity = activity as MainActivity
try {
activity.contentResolver.openOutputStream(data)!!.bufferedWriter().use {
it.write(profiles.toString(2))
}
} catch (e: Exception) {
Timber.w(e)
activity.snackbar(e.readableMessage).show()
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
......
......@@ -21,11 +21,11 @@
package com.github.shadowsocks
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.ShortcutManager
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.Menu
......@@ -42,7 +42,6 @@ import androidx.work.await
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.forEachTry
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.widget.ListHolderListener
......@@ -55,11 +54,6 @@ import kotlinx.coroutines.tasks.await
import timber.log.Timber
class ScannerActivity : AppCompatActivity(), ImageAnalysis.Analyzer {
companion object {
private const val REQUEST_IMPORT = 2
private const val REQUEST_IMPORT_OR_FINISH = 3
}
private val scanner = BarcodeScanning.getClient(BarcodeScannerOptions.Builder().apply {
setBarcodeFormats(Barcode.FORMAT_QR_CODE)
}.build())
......@@ -152,31 +146,25 @@ class ScannerActivity : AppCompatActivity(), ImageAnalysis.Analyzer {
return super.onSupportNavigateUp()
}
private fun startImport(manual: Boolean = false) = startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}, if (manual) REQUEST_IMPORT else REQUEST_IMPORT_OR_FINISH)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_IMPORT, REQUEST_IMPORT_OR_FINISH -> when {
resultCode == Activity.RESULT_OK -> GlobalScope.launch(Dispatchers.Main.immediate) {
onSupportNavigateUp()
val feature = Core.currentProfile?.main
try {
var success = false
data!!.datas.forEachTry { uri ->
if (process(feature) { InputImage.fromFilePath(app, uri) }) success = true
}
Toast.makeText(app, if (success) R.string.action_import_msg else R.string.action_import_err,
Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(app, e.readableMessage, Toast.LENGTH_LONG).show()
}
private fun startImport(manual: Boolean = false) = (if (manual) import else importFinish).launch("image/*")
private val import = registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { importOrFinish(it) }
private val importFinish = registerForActivityResult(ActivityResultContracts.GetMultipleContents()) {
importOrFinish(it, true)
}
private fun importOrFinish(dataUris: List<Uri>, finish: Boolean = false) {
if (dataUris.isNotEmpty()) GlobalScope.launch(Dispatchers.Main.immediate) {
onSupportNavigateUp()
val feature = Core.currentProfile?.main
try {
var success = false
dataUris.forEachTry { uri ->
if (process(feature) { InputImage.fromFilePath(app, uri) }) success = true
}
requestCode == REQUEST_IMPORT_OR_FINISH -> onSupportNavigateUp()
Toast.makeText(app, if (success) R.string.action_import_msg else R.string.action_import_err,
Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(app, e.readableMessage, Toast.LENGTH_LONG).show()
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
} else if (finish) onSupportNavigateUp()
}
}
......@@ -26,9 +26,6 @@ import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
/**
* @author Mygod
*/
open class ToolbarFragment : Fragment() {
lateinit var toolbar: Toolbar
......
......@@ -46,10 +46,9 @@ class PluginConfigurationDialogFragment : EditTextPreferenceDialogFragmentCompat
super.onPrepareDialogBuilder(builder)
val intent = PluginManager.buildIntent(arguments?.getString(PLUGIN_ID_FRAGMENT_TAG)!!,
PluginContract.ACTION_HELP)
val activity = requireActivity()
val activity = activity as ProfileConfigActivity
if (intent.resolveActivity(activity.packageManager) != null) builder.setNeutralButton("?") { _, _ ->
activity.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.text.toString()),
ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)
activity.pluginHelp.launch(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.text.toString()))
}
}
......
......@@ -20,15 +20,14 @@
package com.github.shadowsocks.tv
import android.app.Activity
import android.app.backup.BackupManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.VpnService
import android.os.Bundle
import android.os.RemoteException
import android.text.format.Formatter
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.viewModels
import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
import androidx.lifecycle.observe
......@@ -44,20 +43,12 @@ import com.github.shadowsocks.net.HttpsTest
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.EditTextPreferenceModifiers
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.utils.*
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import timber.log.Timber
class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksConnection.Callback,
OnPreferenceDataStoreChangeListener {
companion object {
private const val REQUEST_CONNECT = 1
private const val REQUEST_REPLACE_PROFILES = 2
private const val REQUEST_EXPORT_PROFILES = 3
}
private lateinit var fab: ListPreference
private lateinit var stats: Preference
private lateinit var controlImport: Preference
......@@ -177,15 +168,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
}
fun startService() {
when {
state != BaseService.State.Stopped -> return
DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(requireContext())
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
}
else -> Core.startService()
}
if (state == BaseService.State.Stopped) connect.launch(null)
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
......@@ -212,18 +195,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
true
}
Key.controlImport -> {
startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*"))
}, REQUEST_REPLACE_PROFILES)
startFilesForResult(replaceProfiles)
true
}
Key.controlExport -> {
startFilesForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = "application/json"
putExtra(Intent.EXTRA_TITLE, "profiles.json") // optional title that can be edited
}, REQUEST_EXPORT_PROFILES)
startFilesForResult(exportProfiles)
true
}
Key.about -> {
......@@ -237,48 +213,42 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
else -> super.onPreferenceTreeClick(preference)
}
private fun startFilesForResult(intent: Intent, requestCode: Int): Boolean {
private fun startFilesForResult(launcher: ActivityResultLauncher<String>) {
try {
startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode)
return false
} catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { }
return launcher.launch("")
} catch (_: ActivityNotFoundException) {
} catch (_: SecurityException) {
}
Toast.makeText(requireContext(), R.string.file_manager_missing, Toast.LENGTH_SHORT).show()
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CONNECT -> if (resultCode == Activity.RESULT_OK) Core.startService() else {
Toast.makeText(requireContext(), R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
Timber.e("Failed to start VpnService from onActivityResult: $data")
}
REQUEST_REPLACE_PROFILES -> {
if (resultCode != Activity.RESULT_OK) return
val context = requireContext()
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
context.contentResolver.openInputStream(it)
}.filterNotNull(), true)
} catch (e: Exception) {
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
populateProfiles()
}
REQUEST_EXPORT_PROFILES -> {
if (resultCode != Activity.RESULT_OK) return
val profiles = ProfileManager.serializeToJson()
val context = requireContext()
if (profiles != null) try {
context.contentResolver.openOutputStream(data?.data!!)!!.bufferedWriter().use {
it.write(profiles.toString(2))
}
} catch (e: Exception) {
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
private val connect = registerForActivityResult(StartService()) {
if (it) Toast.makeText(requireContext(), R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
}
private val replaceProfiles = registerForActivityResult(OpenJson()) { dataUris ->
if (dataUris.isEmpty()) return@registerForActivityResult
val context = requireContext()
try {
ProfileManager.createProfilesFromJson(dataUris.asSequence().map {
context.contentResolver.openInputStream(it)
}.filterNotNull(), true)
} catch (e: Exception) {
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
populateProfiles()
}
private val exportProfiles = registerForActivityResult(SaveJson()) { data ->
if (data != null) ProfileManager.serializeToJson()?.let { profiles ->
val context = requireContext()
try {
context.contentResolver.openOutputStream(data)!!.bufferedWriter().use {
it.write(profiles.toString(2))
}
} catch (e: Exception) {
Timber.w(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment