/******************************************************************************* * * * Copyright (C) 2017 by Max Lv * * Copyright (C) 2017 by Mygod Studio * * * * 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 . * * * *******************************************************************************/ package com.github.shadowsocks import android.app.Activity import android.content.BroadcastReceiver import android.content.Intent import android.os.Build import android.os.Bundle import android.os.UserManager import android.support.design.widget.Snackbar import android.support.v14.preference.SwitchPreference import android.support.v7.app.AlertDialog import android.support.v7.preference.Preference import android.support.v7.preference.PreferenceDataStore import android.support.v7.widget.Toolbar import android.view.MenuItem import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginContract import com.github.shadowsocks.plugin.PluginManager import com.github.shadowsocks.plugin.PluginOptions import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.IconListPreference import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.PluginConfigurationDialogFragment import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.Key import com.takisoft.fix.support.v7.preference.EditTextPreference import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenuItemClickListener, Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener { companion object { private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1 } private var profileId = -1 private lateinit var isProxyApps: SwitchPreference private lateinit var plugin: IconListPreference private lateinit var pluginConfigure: EditTextPreference private lateinit var pluginConfiguration: PluginConfiguration private lateinit var receiver: BroadcastReceiver override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.preferenceDataStore = DataStore.privateStore val activity = requireActivity() profileId = activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1) addPreferencesFromResource(R.xml.pref_profile) if (Build.VERSION.SDK_INT >= 25 && activity.getSystemService(UserManager::class.java).isDemoUser) { findPreference(Key.host).summary = "shadowsocks.example.org" findPreference(Key.remotePort).summary = "1337" findPreference(Key.password).summary = "\u2022".repeat(32) } val serviceMode = DataStore.serviceMode findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy isProxyApps = findPreference(Key.proxyApps) as SwitchPreference isProxyApps.isEnabled = serviceMode == Key.modeVpn isProxyApps.setOnPreferenceClickListener { startActivity(Intent(activity, AppManager::class.java)) isProxyApps.isChecked = true false } findPreference(Key.udpdns).isEnabled = serviceMode != Key.modeProxy plugin = findPreference(Key.plugin) as IconListPreference pluginConfigure = findPreference(Key.pluginConfigure) as EditTextPreference plugin.unknownValueSummary = getString(R.string.plugin_unknown) plugin.setOnPreferenceChangeListener { _, newValue -> pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions, newValue as String) DataStore.plugin = pluginConfiguration.toString() DataStore.dirty = true pluginConfigure.isEnabled = newValue.isNotEmpty() pluginConfigure.text = pluginConfiguration.selectedOptions.toString() if (PluginManager.fetchPlugins()[newValue]?.trusted == false) Snackbar.make(view!!, R.string.plugin_untrusted, Snackbar.LENGTH_LONG).show() true } pluginConfigure.onPreferenceChangeListener = this initPlugins() receiver = app.listenForPackageChanges(false) { initPlugins() } DataStore.privateStore.registerChangeListener(this) } private fun initPlugins() { val plugins = PluginManager.fetchPlugins() plugin.entries = plugins.map { it.value.label }.toTypedArray() plugin.entryValues = plugins.map { it.value.id }.toTypedArray() plugin.entryIcons = plugins.map { it.value.icon }.toTypedArray() plugin.entryPackageNames = plugins.map { it.value.packageName }.toTypedArray() pluginConfiguration = PluginConfiguration(DataStore.plugin) plugin.value = pluginConfiguration.selected plugin.init() plugin.checkSummary() pluginConfigure.isEnabled = pluginConfiguration.selected.isNotEmpty() pluginConfigure.text = pluginConfiguration.selectedOptions.toString() } private fun showPluginEditor() { val bundle = Bundle() bundle.putString("key", Key.pluginConfigure) bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected) displayPreferenceDialog(PluginConfigurationDialogFragment(), Key.pluginConfigure, bundle) } fun saveAndExit() { val profile = ProfileManager.getProfile(profileId) ?: Profile() profile.id = profileId profile.deserialize() ProfileManager.updateProfile(profile) ProfilesFragment.instance?.profilesAdapter?.deepRefreshId(profileId) if (DataStore.profileId == profileId && DataStore.directBootAware) DirectBoot.update() requireActivity().finish() } override fun onResume() { super.onResume() isProxyApps.isChecked = DataStore.proxyApps // fetch proxyApps updated by AppManager } override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try { val selected = pluginConfiguration.selected pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions + (pluginConfiguration.selected to PluginOptions(selected, newValue as? String?)), selected) DataStore.plugin = pluginConfiguration.toString() DataStore.dirty = true true } catch (exc: IllegalArgumentException) { Snackbar.make(view!!, exc.localizedMessage, Snackbar.LENGTH_LONG).show() false } override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { if (key != Key.proxyApps && findPreference(key) != null) DataStore.dirty = true } override fun onDisplayPreferenceDialog(preference: Preference) { if (preference.key == Key.pluginConfigure) { val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE) if (intent.resolveActivity(requireContext().packageManager) != null) startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString()), REQUEST_CODE_PLUGIN_CONFIGURE) else { showPluginEditor() } } else super.onDisplayPreferenceDialog(preference) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == 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() } else super.onActivityResult(requestCode, resultCode, data) } override fun onMenuItemClick(item: MenuItem) = when (item.itemId) { R.id.action_delete -> { val activity = requireActivity() AlertDialog.Builder(activity) .setTitle(R.string.delete_confirm_prompt) .setPositiveButton(R.string.yes, { _, _ -> ProfileManager.delProfile(profileId) activity.finish() }) .setNegativeButton(R.string.no, null) .create() .show() true } R.id.action_apply -> { saveAndExit() true } else -> false } override fun onDestroy() { DataStore.privateStore.unregisterChangeListener(this) app.unregisterReceiver(receiver) super.onDestroy() } }