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