Add more material design (3)
This commit is contained in:
@@ -39,32 +39,26 @@
|
||||
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.app.{Activity, ProgressDialog}
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream}
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.{ClipData, ClipboardManager, Context, SharedPreferences}
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.{Bitmap, PixelFormat}
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.{Bundle, Handler}
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import android.view.WindowManager
|
||||
import android.widget._
|
||||
import android.view.{Menu, MenuItem, View, ViewGroup, WindowManager}
|
||||
import android.widget.AbsListView.OnScrollListener
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener
|
||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
|
||||
import java.io.{ByteArrayOutputStream, ByteArrayInputStream, InputStream}
|
||||
import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, ImageLoaderConfiguration}
|
||||
import android.widget._
|
||||
import com.github.shadowsocks.utils.{Key, Scheme, Utils}
|
||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
|
||||
import com.github.shadowsocks.utils.{Utils, Scheme, Key}
|
||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
|
||||
import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, ImageLoaderConfiguration}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
@@ -105,7 +99,7 @@ object AppManager {
|
||||
}
|
||||
}
|
||||
|
||||
class AppManager extends Activity with OnCheckedChangeListener with OnClickListener {
|
||||
class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnClickListener {
|
||||
|
||||
val MSG_LOAD_START = 1
|
||||
val MSG_LOAD_FINISH = 2
|
||||
|
||||
@@ -57,14 +57,14 @@ class ParserActivity extends Activity {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.add_profile_dialog)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.yes, ((dialog: DialogInterface, id: Int) => {
|
||||
.setPositiveButton(android.R.string.yes, ((dialog: DialogInterface, id: Int) => {
|
||||
Parser.parse(data) match {
|
||||
case Some(profile) => addProfile(profile)
|
||||
case _ => // ignore
|
||||
}
|
||||
dialog.dismiss()
|
||||
}): DialogInterface.OnClickListener)
|
||||
.setNegativeButton(R.string.no, ((dialog: DialogInterface, id: Int) => {
|
||||
.setNegativeButton(android.R.string.no, ((dialog: DialogInterface, id: Int) => {
|
||||
dialog.dismiss()
|
||||
finish()
|
||||
}): DialogInterface.OnClickListener)
|
||||
@@ -90,7 +90,7 @@ class ParserActivity extends Activity {
|
||||
h.postDelayed(() => {
|
||||
val profileManager =
|
||||
new ProfileManager(PreferenceManager.getDefaultSharedPreferences(getBaseContext),
|
||||
getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
|
||||
ShadowsocksApplication.dbHelper)
|
||||
profileManager.createOrUpdateProfile(profile)
|
||||
profileManager.reload(profile.id)
|
||||
h.sendEmptyMessage(0)
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.{DialogInterface, Intent}
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder
|
||||
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
|
||||
import android.support.v7.widget.helper.ItemTouchHelper
|
||||
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
|
||||
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
|
||||
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
|
||||
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
|
||||
import android.widget.{LinearLayout, ImageView, CheckedTextView, Toast}
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.utils.{Parser, Utils}
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import net.glxn.qrgen.android.QRCode
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener {
|
||||
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
|
||||
private var item: Profile = _
|
||||
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
|
||||
itemView.setOnClickListener(this)
|
||||
|
||||
{
|
||||
val qrcode = itemView.findViewById(R.id.qrcode)
|
||||
qrcode.setOnClickListener((v: View) => {
|
||||
val image = new ImageView(ProfileManagerActivity.this)
|
||||
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
|
||||
val qrcode = QRCode.from(Parser.generate(item))
|
||||
.withSize(Utils.dpToPx(ProfileManagerActivity.this, 250), Utils.dpToPx(ProfileManagerActivity.this, 250))
|
||||
.asInstanceOf[QRCode]
|
||||
image.setImageBitmap(qrcode.bitmap())
|
||||
|
||||
new AlertDialog.Builder(ProfileManagerActivity.this)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(getString(R.string.close),
|
||||
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
|
||||
.setView(image)
|
||||
.create()
|
||||
.show()
|
||||
})
|
||||
qrcode.setOnLongClickListener((v: View) => {
|
||||
Utils.positionToast(Toast.makeText(ProfileManagerActivity.this, R.string.qrcode, Toast.LENGTH_SHORT), qrcode,
|
||||
getWindow, 0, Utils.dpToPx(ProfileManagerActivity.this, 8)).show
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
def bind(item: Profile) {
|
||||
text.setText(item.name)
|
||||
text.setChecked(item.id == ShadowsocksApplication.profileId)
|
||||
this.item = item
|
||||
}
|
||||
|
||||
def onClick(v: View) = {
|
||||
ShadowsocksApplication.profileId(item.id)
|
||||
finish
|
||||
}
|
||||
}
|
||||
|
||||
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
|
||||
private val recycleBin = new ArrayBuffer[(Int, Profile)]
|
||||
private var profiles = new ArrayBuffer[Profile]
|
||||
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]())
|
||||
|
||||
def getItemCount = profiles.length
|
||||
|
||||
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i))
|
||||
|
||||
def onCreateViewHolder(vg: ViewGroup, i: Int) =
|
||||
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
|
||||
|
||||
def add(item: Profile) {
|
||||
removedSnackbar.dismiss
|
||||
commitRemoves
|
||||
val pos = getItemCount
|
||||
if (ShadowsocksApplication.profileManager.createOrUpdateProfile(item)) {
|
||||
profiles += item
|
||||
notifyItemInserted(pos)
|
||||
}
|
||||
}
|
||||
|
||||
def remove(pos: Int) {
|
||||
recycleBin.append((pos, profiles(pos)))
|
||||
profiles.remove(pos)
|
||||
notifyItemRemoved(pos)
|
||||
}
|
||||
def undoRemoves {
|
||||
for ((index, item) <- recycleBin.reverseIterator) {
|
||||
profiles.insert(index, item)
|
||||
notifyItemInserted(index)
|
||||
}
|
||||
recycleBin.clear
|
||||
}
|
||||
def commitRemoves {
|
||||
for ((index, item) <- recycleBin) {
|
||||
ShadowsocksApplication.profileManager.delProfile(item.id)
|
||||
if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1)
|
||||
}
|
||||
recycleBin.clear
|
||||
}
|
||||
}
|
||||
|
||||
private lazy val profilesAdapter = new ProfilesAdapter
|
||||
private var removedSnackbar: Snackbar = _
|
||||
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.layout_profiles)
|
||||
|
||||
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
|
||||
toolbar.setTitle(R.string.profiles)
|
||||
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha)
|
||||
toolbar.setNavigationOnClickListener((v: View) => {
|
||||
val intent = getParentActivityIntent
|
||||
if (intent == null) finish else navigateUpTo(intent)
|
||||
})
|
||||
toolbar.inflateMenu(R.menu.add_profile_methods)
|
||||
toolbar.setOnMenuItemClickListener(this)
|
||||
|
||||
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
|
||||
profilesList.setLayoutManager(new LinearLayoutManager(this))
|
||||
profilesList.setItemAnimator(new DefaultItemAnimator)
|
||||
profilesList.setAdapter(profilesAdapter)
|
||||
removedSnackbar = Snackbar.make(findViewById(android.R.id.content), R.string.removed, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener)
|
||||
removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener {
|
||||
def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves
|
||||
def onViewAttachedToWindow(v: View) = ()
|
||||
})
|
||||
new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) {
|
||||
def onSwiped(viewHolder: ViewHolder, direction: Int) = {
|
||||
profilesAdapter.remove(viewHolder.getAdapterPosition)
|
||||
removedSnackbar.show
|
||||
}
|
||||
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO?
|
||||
}).attachToRecyclerView(profilesList)
|
||||
}
|
||||
|
||||
override def onDestroy {
|
||||
super.onDestroy
|
||||
profilesAdapter.commitRemoves
|
||||
}
|
||||
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
|
||||
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
if (scanResult != null) Parser.parse(scanResult.getContents) match {
|
||||
case Some(profile) => profilesAdapter.add(profile)
|
||||
case _ => // ignore
|
||||
}
|
||||
}
|
||||
|
||||
def onMenuItemClick(item: MenuItem) = item.getItemId match {
|
||||
case R.id.scan_qr_code =>
|
||||
val integrator = new IntentIntegrator(this)
|
||||
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
|
||||
list.add("tw.com.quickmark")
|
||||
integrator.setTargetApplications(list)
|
||||
integrator.initiateScan()
|
||||
true
|
||||
case R.id.manual_settings =>
|
||||
ShadowsocksApplication.profileManager.reload(-1)
|
||||
ShadowsocksApplication.profileId(ShadowsocksApplication.profileManager.save.id)
|
||||
finish
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
@@ -45,35 +45,28 @@ import java.util.Locale
|
||||
import android.app.backup.BackupManager
|
||||
import android.app.{Activity, AlertDialog, ProgressDialog}
|
||||
import android.content._
|
||||
import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.content.res.AssetManager
|
||||
import android.graphics.{Bitmap, Color, Typeface}
|
||||
import android.net.{Uri, VpnService}
|
||||
import android.os._
|
||||
import android.preference.{Preference, PreferenceManager, SwitchPreference}
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.preference.{Preference, SwitchPreference}
|
||||
import android.support.design.widget.{FloatingActionButton, Snackbar}
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.{DisplayMetrics, Log}
|
||||
import android.util.Log
|
||||
import android.view.{View, ViewGroup, ViewParent}
|
||||
import android.webkit.{WebView, WebViewClient}
|
||||
import android.widget._
|
||||
import com.github.jorgecastilloprz.FABProgressCircle
|
||||
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
|
||||
import com.github.shadowsocks.database._
|
||||
import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference}
|
||||
import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, SummaryEditTextPreference}
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||
import com.google.android.gms.analytics.HitBuilders
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import com.joanzapata.android.iconify.IconDrawable
|
||||
import com.joanzapata.android.iconify.Iconify.IconValue
|
||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
|
||||
import net.glxn.qrgen.android.QRCode
|
||||
import net.simonvt.menudrawer.MenuDrawer
|
||||
|
||||
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
@@ -86,7 +79,7 @@ class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout:
|
||||
|
||||
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = {
|
||||
val text = imageUri.substring(Scheme.PROFILE.length)
|
||||
val size = Utils.dpToPx(context, 16).toInt
|
||||
val size = Utils.dpToPx(context, 16)
|
||||
val idx = text.getBytes.last % 6
|
||||
val color = Seq(Color.MAGENTA, Color.GREEN, Color.YELLOW, Color.BLUE, Color.DKGRAY, Color.CYAN)(
|
||||
idx)
|
||||
@@ -126,12 +119,10 @@ object Shadowsocks {
|
||||
val REQUEST_CONNECT = 1
|
||||
|
||||
val PREFS_NAME = "Shadowsocks"
|
||||
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey,
|
||||
Key.encMethod)
|
||||
val FEATRUE_PREFS = Array(Key.route, Key.isGlobalProxy, Key.proxyedApps,
|
||||
Key.isUdpDns, Key.isAuth, Key.isIpv6, Key.isAutoConnect)
|
||||
|
||||
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, Executable.TUN2SOCKS)
|
||||
val PROXY_PREFS = Array(Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod)
|
||||
val FEATURE_PREFS = Array(Key.route, Key.isGlobalProxy, Key.proxyedApps, Key.isUdpDns, Key.isAuth, Key.isIpv6)
|
||||
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
|
||||
Executable.TUN2SOCKS)
|
||||
|
||||
// Helper functions
|
||||
def updateDropDownPreference(pref: Preference, value: String) {
|
||||
@@ -148,18 +139,13 @@ object Shadowsocks {
|
||||
pref.asInstanceOf[SummaryEditTextPreference].setText(value)
|
||||
}
|
||||
|
||||
def updateProfileEditTextPreference(pref: Preference, value: String) {
|
||||
pref.asInstanceOf[ProfileEditTextPreference].resetSummary(value)
|
||||
pref.asInstanceOf[ProfileEditTextPreference].setText(value)
|
||||
}
|
||||
|
||||
def updateSwitchPreference(pref: Preference, value: Boolean) {
|
||||
pref.asInstanceOf[SwitchPreference].setChecked(value)
|
||||
}
|
||||
|
||||
def updatePreference(pref: Preference, name: String, profile: Profile) {
|
||||
name match {
|
||||
case Key.profileName => updateProfileEditTextPreference(pref, profile.name)
|
||||
case Key.profileName => updateSummaryEditTextPreference(pref, profile.name)
|
||||
case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
|
||||
case Key.remotePort => updateSummaryEditTextPreference(pref, profile.remotePort.toString)
|
||||
case Key.localPort => updateSummaryEditTextPreference(pref, profile.localPort.toString)
|
||||
@@ -176,14 +162,8 @@ object Shadowsocks {
|
||||
}
|
||||
|
||||
class Shadowsocks
|
||||
extends AppCompatActivity
|
||||
with MenuAdapter.MenuListener {
|
||||
|
||||
// Flags
|
||||
val MSG_CRASH_RECOVER: Int = 1
|
||||
val STATE_MENUDRAWER = "com.github.shadowsocks.menuDrawer"
|
||||
val STATE_ACTIVE_VIEW_ID = "com.github.shadowsocks.activeViewId"
|
||||
var singlePane: Int = -1
|
||||
extends AppCompatActivity {
|
||||
import Shadowsocks._
|
||||
|
||||
// Variables
|
||||
private var serviceStarted = false
|
||||
@@ -217,8 +197,8 @@ class Shadowsocks
|
||||
if (fab != null) fab.setEnabled(true)
|
||||
stateUpdate()
|
||||
|
||||
if (!status.getBoolean(getVersionName, false)) {
|
||||
status.edit.putBoolean(getVersionName, true).apply()
|
||||
if (!status.getBoolean(ShadowsocksApplication.getVersionName, false)) {
|
||||
status.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
|
||||
recovery()
|
||||
}
|
||||
}
|
||||
@@ -236,33 +216,12 @@ class Shadowsocks
|
||||
|
||||
private lazy val preferences =
|
||||
getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings]
|
||||
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||
private lazy val preferenceReceiver = new PreferenceBroadcastReceiver
|
||||
private lazy val drawer = MenuDrawer.attach(this)
|
||||
private lazy val menuAdapter = new MenuAdapter(this, getMenuList)
|
||||
private lazy val listView = new ListView(this)
|
||||
private lazy val profileManager =
|
||||
new ProfileManager(settings, getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
|
||||
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
|
||||
private lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||
private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700)
|
||||
private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_600)
|
||||
|
||||
var handler = new Handler()
|
||||
|
||||
def isSinglePane: Boolean = {
|
||||
if (singlePane == -1) {
|
||||
val metrics = new DisplayMetrics()
|
||||
getWindowManager.getDefaultDisplay.getMetrics(metrics)
|
||||
val widthPixels = metrics.widthPixels
|
||||
val scaleFactor = metrics.density
|
||||
val widthDp = widthPixels / scaleFactor
|
||||
|
||||
singlePane = if (widthDp <= 720) 1 else 0
|
||||
}
|
||||
singlePane == 1
|
||||
}
|
||||
|
||||
private def changeSwitch(checked: Boolean) {
|
||||
serviceStarted = checked
|
||||
fab.setImageResource(if (checked) R.drawable.ic_cloud else R.drawable.ic_cloud_off)
|
||||
@@ -359,18 +318,6 @@ class Shadowsocks
|
||||
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
|
||||
}
|
||||
|
||||
private def getVersionName: String = {
|
||||
var version: String = null
|
||||
try {
|
||||
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
|
||||
version = pi.versionName
|
||||
} catch {
|
||||
case e: PackageManager.NameNotFoundException =>
|
||||
version = "Package name not found"
|
||||
}
|
||||
version
|
||||
}
|
||||
|
||||
def isTextEmpty(s: String, msg: String): Boolean = {
|
||||
if (s != null && s.length > 0) return false
|
||||
Snackbar.make(getWindow.getDecorView.findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).show
|
||||
@@ -413,74 +360,58 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
def initAdView() {
|
||||
if (settings.getString(Key.proxy, "") == "198.199.101.152") {
|
||||
val layoutView = {
|
||||
if (Build.VERSION.SDK_INT > 10) {
|
||||
drawer.getContentContainer.getChildAt(0)
|
||||
} else {
|
||||
getLayoutView(drawer.getContentContainer.getParent)
|
||||
}
|
||||
}
|
||||
if (layoutView != null) {
|
||||
val adView = new AdView(this)
|
||||
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
|
||||
adView.setAdSize(AdSize.SMART_BANNER)
|
||||
layoutView.asInstanceOf[ViewGroup].addView(adView, 0)
|
||||
adView.loadAd(new AdRequest.Builder().build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def setContentView(layoutResId: Int) {
|
||||
drawer.setContentView(layoutResId)
|
||||
initAdView()
|
||||
onContentChanged()
|
||||
}
|
||||
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.layout_main)
|
||||
if (ShadowsocksApplication.proxy == "198.199.101.152") {
|
||||
val adView = new AdView(this)
|
||||
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
|
||||
adView.setAdSize(AdSize.SMART_BANNER)
|
||||
preferences.getView.asInstanceOf[ViewGroup].addView(adView, 0) // TODO: test
|
||||
adView.loadAd(new AdRequest.Builder().build())
|
||||
}
|
||||
|
||||
// Update the profile
|
||||
if (!status.getBoolean(getVersionName, false)) {
|
||||
currentProfile = profileManager.create()
|
||||
if (!status.getBoolean(ShadowsocksApplication.getVersionName, false)) {
|
||||
currentProfile = ShadowsocksApplication.profileManager.create()
|
||||
}
|
||||
|
||||
// Initialize the profile
|
||||
currentProfile = {
|
||||
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse currentProfile
|
||||
}
|
||||
|
||||
// Initialize drawer
|
||||
menuAdapter.setActiveId(settings.getInt(Key.profileId, -1))
|
||||
menuAdapter.setListener(this)
|
||||
listView.setAdapter(menuAdapter)
|
||||
drawer.setMenuView(listView)
|
||||
|
||||
if (Utils.isLollipopOrAbove) {
|
||||
drawer.setDrawerIndicatorEnabled(false)
|
||||
} else {
|
||||
// The drawable that replaces the up indicator in the action bar
|
||||
drawer.setSlideDrawable(R.drawable.ic_drawer)
|
||||
// Whether the previous drawable should be shown
|
||||
drawer.setDrawerIndicatorEnabled(true)
|
||||
}
|
||||
|
||||
if (!isSinglePane) {
|
||||
drawer.openMenu(false)
|
||||
ShadowsocksApplication.currentProfile getOrElse currentProfile
|
||||
}
|
||||
|
||||
// Initialize action bar
|
||||
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
|
||||
toolbar.setTitle(getTitle)
|
||||
toolbar.setTitle(getString(R.string.screen_name))
|
||||
toolbar.setTitleTextAppearance(toolbar.getContext, R.style.Toolbar_Logo)
|
||||
val field = classOf[Toolbar].getDeclaredField("mTitleTextView")
|
||||
field.setAccessible(true)
|
||||
val title: TextView = field.get(toolbar).asInstanceOf[TextView]
|
||||
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
|
||||
if (tf != null) title.setTypeface(tf)
|
||||
title.setOnClickListener((view: View) => {
|
||||
ShadowsocksApplication.track(TAG, "about")
|
||||
showAbout()
|
||||
})
|
||||
title.setOnLongClickListener((v: View) => {
|
||||
if (Utils.isLollipopOrAbove && bgService != null
|
||||
&& (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
|
||||
val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)
|
||||
status.edit().putBoolean(Key.isNAT, natEnabled).apply()
|
||||
deattachService()
|
||||
attachService()
|
||||
if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState))
|
||||
Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
|
||||
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
|
||||
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
|
||||
fab.setOnClickListener((v: View) => {
|
||||
@@ -503,29 +434,9 @@ class Shadowsocks
|
||||
})
|
||||
fab.setOnLongClickListener((v: View) => {
|
||||
Utils.positionToast(Toast.makeText(this, if (serviceStarted) R.string.stop else R.string.connect,
|
||||
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8).toInt).show
|
||||
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show
|
||||
true
|
||||
})
|
||||
toolbar.setNavigationIcon(R.drawable.ic_drawer)
|
||||
toolbar.setNavigationOnClickListener((v: View) => drawer.toggleMenu())
|
||||
title.setOnLongClickListener((v: View) => {
|
||||
if (Utils.isLollipopOrAbove && bgService != null
|
||||
&& (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
|
||||
val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)
|
||||
status.edit().putBoolean(Key.isNAT, natEnabled).apply()
|
||||
deattachService()
|
||||
attachService()
|
||||
if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState))
|
||||
Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
|
||||
// Register broadcast receiver
|
||||
registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS))
|
||||
|
||||
// Bind to the service
|
||||
handler.post(() => {
|
||||
@@ -555,222 +466,29 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
override def onRestoreInstanceState(inState: Bundle) {
|
||||
super.onRestoreInstanceState(inState)
|
||||
drawer.restoreState(inState.getParcelable(STATE_MENUDRAWER))
|
||||
}
|
||||
|
||||
override def onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putParcelable(STATE_MENUDRAWER, drawer.saveState())
|
||||
outState.putInt(STATE_ACTIVE_VIEW_ID, currentProfile.id)
|
||||
}
|
||||
|
||||
override def onBackPressed() {
|
||||
val drawerState = drawer.getDrawerState
|
||||
if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING) {
|
||||
drawer.closeMenu()
|
||||
return
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override def onActiveViewChanged(v: View, pos: Int) {
|
||||
drawer.setActiveView(v, pos)
|
||||
}
|
||||
|
||||
def newProfile(id: Int) {
|
||||
|
||||
val builder = new AlertDialog.Builder(this)
|
||||
builder
|
||||
.setTitle(R.string.add_profile)
|
||||
.setItems(R.array.add_profile_methods, ((dialog: DialogInterface, which: Int) => which match {
|
||||
case 0 =>
|
||||
dialog.dismiss()
|
||||
val h = showProgress(R.string.loading)
|
||||
h.postDelayed(() => {
|
||||
val integrator = new IntentIntegrator(Shadowsocks.this)
|
||||
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
|
||||
list.add("tw.com.quickmark")
|
||||
integrator.setTargetApplications(list)
|
||||
integrator.initiateScan()
|
||||
h.sendEmptyMessage(0)
|
||||
}, 600)
|
||||
case 1 =>
|
||||
dialog.dismiss()
|
||||
addProfile(id)
|
||||
case _ =>
|
||||
}): DialogInterface.OnClickListener)
|
||||
builder.create().show()
|
||||
}
|
||||
|
||||
def reloadProfile() {
|
||||
drawer.closeMenu(true)
|
||||
currentProfile = ShadowsocksApplication.currentProfile match {
|
||||
case Some(profile) => profile // updated
|
||||
case None => // removed
|
||||
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]())
|
||||
ShadowsocksApplication.profileId(if (profiles.isEmpty) -1 else profiles.head.id)
|
||||
ShadowsocksApplication.currentProfile getOrElse currentProfile
|
||||
}
|
||||
|
||||
val h = showProgress(R.string.loading)
|
||||
|
||||
handler.postDelayed(() => {
|
||||
currentProfile = {
|
||||
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse currentProfile
|
||||
}
|
||||
menuAdapter.updateList(getMenuList, currentProfile.id)
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
h.sendEmptyMessage(0)
|
||||
}, 600)
|
||||
updatePreferenceScreen()
|
||||
}
|
||||
|
||||
def addProfile(profile: Profile) {
|
||||
drawer.closeMenu(true)
|
||||
currentProfile = profile
|
||||
ShadowsocksApplication.profileManager.createOrUpdateProfile(currentProfile)
|
||||
ShadowsocksApplication.profileManager.reload(currentProfile.id)
|
||||
|
||||
val h = showProgress(R.string.loading)
|
||||
|
||||
handler.postDelayed(() => {
|
||||
currentProfile = profile
|
||||
profileManager.createOrUpdateProfile(currentProfile)
|
||||
profileManager.reload(currentProfile.id)
|
||||
menuAdapter.updateList(getMenuList, currentProfile.id)
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
h.sendEmptyMessage(0)
|
||||
}, 600)
|
||||
}
|
||||
|
||||
def addProfile(id: Int) {
|
||||
drawer.closeMenu(true)
|
||||
|
||||
val h = showProgress(R.string.loading)
|
||||
|
||||
handler.postDelayed(() => {
|
||||
currentProfile = profileManager.reload(id)
|
||||
profileManager.save()
|
||||
menuAdapter.updateList(getMenuList, currentProfile.id)
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
h.sendEmptyMessage(0)
|
||||
}, 600)
|
||||
}
|
||||
|
||||
def updateProfile(id: Int) {
|
||||
drawer.closeMenu(true)
|
||||
|
||||
val h = showProgress(R.string.loading)
|
||||
|
||||
handler.postDelayed(() => {
|
||||
currentProfile = profileManager.reload(id)
|
||||
menuAdapter.setActiveId(id)
|
||||
menuAdapter.notifyDataSetChanged()
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
h.sendEmptyMessage(0)
|
||||
}, 600)
|
||||
}
|
||||
|
||||
def delProfile(id: Int): Boolean = {
|
||||
drawer.closeMenu(true)
|
||||
|
||||
val profile = profileManager.getProfile(id)
|
||||
|
||||
if (profile.isEmpty) return false
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(String.format(Locale.ENGLISH, getString(R.string.remove_profile), profile.get.name))
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(R.string.no,
|
||||
((dialog: DialogInterface, i: Int) => dialog.cancel()): DialogInterface.OnClickListener)
|
||||
.setPositiveButton(R.string.yes, ((dialog: DialogInterface, i: Int) => {
|
||||
profileManager.delProfile(id)
|
||||
val profileId = {
|
||||
val profiles = profileManager.getAllProfiles.getOrElse(List[Profile]())
|
||||
if (profiles.isEmpty) -1 else profiles.head.id
|
||||
}
|
||||
currentProfile = profileManager.load(profileId)
|
||||
menuAdapter.updateList(getMenuList, currentProfile.id)
|
||||
|
||||
updatePreferenceScreen()
|
||||
|
||||
dialog.dismiss()
|
||||
}): DialogInterface.OnClickListener)
|
||||
.create()
|
||||
.show()
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
def getProfileList: List[Item] = {
|
||||
val list = profileManager.getAllProfiles getOrElse List[Profile]()
|
||||
list.map(p => new IconItem(p.id, p.name, -1, updateProfile, delProfile))
|
||||
}
|
||||
|
||||
def getMenuList: List[Any] = {
|
||||
|
||||
val buf = new ListBuffer[Any]()
|
||||
|
||||
buf += new Category(getString(R.string.profiles))
|
||||
|
||||
buf ++= getProfileList
|
||||
|
||||
buf +=
|
||||
new DrawableItem(-400, getString(R.string.add_profile), new IconDrawable(this, IconValue.fa_plus_circle)
|
||||
.colorRes(android.R.color.darker_gray).sizeDp(26), newProfile)
|
||||
|
||||
buf += new Category(getString(R.string.settings))
|
||||
|
||||
buf += new DrawableItem(-100, getString(R.string.recovery), new IconDrawable(this, IconValue.fa_recycle)
|
||||
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(Shadowsocks.TAG)
|
||||
.setAction("reset")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
recovery()
|
||||
})
|
||||
|
||||
buf +=
|
||||
new DrawableItem(-200, getString(R.string.flush_dnscache), new IconDrawable(this, IconValue.fa_refresh)
|
||||
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(Shadowsocks.TAG)
|
||||
.setAction("flush_dnscache")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
flushDnsCache()
|
||||
})
|
||||
|
||||
buf +=
|
||||
new DrawableItem(-300, getString(R.string.qrcode), new IconDrawable(this, IconValue.fa_qrcode)
|
||||
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(Shadowsocks.TAG)
|
||||
.setAction("qrcode")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
showQrCode()
|
||||
})
|
||||
|
||||
buf += new DrawableItem(-400, getString(R.string.about), new IconDrawable(this, IconValue.fa_info_circle)
|
||||
.colorRes(android.R.color.darker_gray).sizeDp(26), _ => {
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(Shadowsocks.TAG)
|
||||
.setAction("about")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
showAbout()
|
||||
})
|
||||
|
||||
buf.toList
|
||||
updatePreferenceScreen()
|
||||
}
|
||||
|
||||
protected override def onPause() {
|
||||
super.onPause()
|
||||
ShadowsocksApplication.profileManager.save
|
||||
prepared = false
|
||||
}
|
||||
|
||||
@@ -778,19 +496,19 @@ class Shadowsocks
|
||||
if (bgService != null) {
|
||||
bgService.getState match {
|
||||
case State.CONNECTING =>
|
||||
fab.setBackgroundTintList(greyTint);
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
fabProgressCircle.show()
|
||||
changeSwitch(checked = true)
|
||||
case State.CONNECTED =>
|
||||
fab.setBackgroundTintList(greenTint);
|
||||
fab.setBackgroundTintList(greenTint)
|
||||
fabProgressCircle.hide()
|
||||
changeSwitch(checked = true)
|
||||
case State.STOPPING =>
|
||||
fab.setBackgroundTintList(greyTint);
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
fabProgressCircle.show()
|
||||
changeSwitch(checked = false)
|
||||
case _ =>
|
||||
fab.setBackgroundTintList(greyTint);
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
fabProgressCircle.hide()
|
||||
changeSwitch(checked = false)
|
||||
}
|
||||
@@ -803,10 +521,8 @@ class Shadowsocks
|
||||
stateUpdate()
|
||||
ConfigUtils.refresh(this)
|
||||
|
||||
// Check if profile list changed
|
||||
val id = settings.getInt(Key.profileId, -1)
|
||||
if (id != -1 && id != currentProfile.id)
|
||||
reloadProfile()
|
||||
// Check if current profile changed
|
||||
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
||||
}
|
||||
|
||||
private def setPreferenceEnabled(enabled: Boolean) {
|
||||
@@ -816,7 +532,7 @@ class Shadowsocks
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
for (name <- Shadowsocks.FEATURE_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
if (pref != null) {
|
||||
if (Seq(Key.isGlobalProxy, Key.proxyedApps)
|
||||
@@ -831,11 +547,12 @@ class Shadowsocks
|
||||
|
||||
private def updatePreferenceScreen() {
|
||||
val profile = currentProfile
|
||||
Shadowsocks.updatePreference(preferences.findPreference(Key.profileName), Key.profileName, profile)
|
||||
for (name <- Shadowsocks.PROXY_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, profile)
|
||||
}
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
for (name <- Shadowsocks.FEATURE_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, profile)
|
||||
}
|
||||
@@ -853,7 +570,6 @@ class Shadowsocks
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
deattachService()
|
||||
unregisterReceiver(preferenceReceiver)
|
||||
new BackupManager(this).dataChanged()
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
@@ -881,13 +597,12 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
def reset() {
|
||||
|
||||
crashRecovery()
|
||||
|
||||
install()
|
||||
}
|
||||
|
||||
private def recovery() {
|
||||
def recovery() {
|
||||
serviceStop()
|
||||
val h = showProgress(R.string.recovering)
|
||||
Future {
|
||||
@@ -896,26 +611,7 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
private def dp2px(dp: Int): Int =
|
||||
Math.round(dp * (getBaseContext.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
|
||||
|
||||
private def showQrCode() {
|
||||
val image = new ImageView(this)
|
||||
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
|
||||
val qrcode = QRCode.from(Parser.generate(currentProfile))
|
||||
.withSize(dp2px(250), dp2px(250)).asInstanceOf[QRCode]
|
||||
image.setImageBitmap(qrcode.bitmap())
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(getString(R.string.close),
|
||||
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
|
||||
.setView(image)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private def flushDnsCache() {
|
||||
def flushDnsCache() {
|
||||
val h = showProgress(R.string.flushing)
|
||||
Future {
|
||||
Utils.toggleAirplaneMode(getBaseContext)
|
||||
@@ -923,23 +619,13 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
|
||||
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
if (scanResult != null) {
|
||||
Parser.parse(scanResult.getContents) match {
|
||||
case Some(profile) => addProfile(profile)
|
||||
case _ => // ignore
|
||||
}
|
||||
} else {
|
||||
resultCode match {
|
||||
case Activity.RESULT_OK =>
|
||||
prepared = true
|
||||
serviceStart()
|
||||
case _ =>
|
||||
cancelStart()
|
||||
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
|
||||
}
|
||||
}
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) = resultCode match {
|
||||
case Activity.RESULT_OK =>
|
||||
prepared = true
|
||||
serviceStart()
|
||||
case _ =>
|
||||
cancelStart()
|
||||
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
|
||||
}
|
||||
|
||||
var isRoot: Option[Boolean] = None
|
||||
@@ -953,12 +639,12 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
def checkText(key: String): Boolean = {
|
||||
val text = settings.getString(key, "")
|
||||
val text = ShadowsocksApplication.settings.getString(key, "")
|
||||
!isTextEmpty(text, getString(R.string.proxy_empty))
|
||||
}
|
||||
|
||||
def checkNumber(key: String, low: Boolean): Boolean = {
|
||||
val text = settings.getString(key, "")
|
||||
val text = ShadowsocksApplication.settings.getString(key, "")
|
||||
if (isTextEmpty(text, getString(R.string.port_empty))) return false
|
||||
try {
|
||||
val port: Int = Integer.valueOf(text)
|
||||
@@ -978,7 +664,7 @@ class Shadowsocks
|
||||
|
||||
/** Called when connect button is clicked. */
|
||||
def serviceStart() {
|
||||
bgService.start(ConfigUtils.load(settings))
|
||||
bgService.start(ConfigUtils.load(ShadowsocksApplication.settings))
|
||||
|
||||
if (isVpnEnabled) {
|
||||
changeSwitch(checked = false)
|
||||
@@ -986,7 +672,6 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
private def showAbout() {
|
||||
|
||||
val web = new WebView(this)
|
||||
web.loadUrl("file:///android_asset/pages/about.html")
|
||||
web.setWebViewClient(new WebViewClient() {
|
||||
@@ -996,18 +681,10 @@ class Shadowsocks
|
||||
}
|
||||
})
|
||||
|
||||
var versionName = ""
|
||||
try {
|
||||
versionName = getPackageManager.getPackageInfo(getPackageName, 0).versionName
|
||||
} catch {
|
||||
case ex: PackageManager.NameNotFoundException =>
|
||||
versionName = ""
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, versionName))
|
||||
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, ShadowsocksApplication.getVersionName))
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(getString(R.string.ok_iknow),
|
||||
.setNegativeButton(getString(android.R.string.ok),
|
||||
((dialog: DialogInterface, id: Int) => dialog.cancel()): DialogInterface.OnClickListener)
|
||||
.setView(web)
|
||||
.create()
|
||||
@@ -1026,13 +703,13 @@ class Shadowsocks
|
||||
handler.post(() => if (state != s) {
|
||||
s match {
|
||||
case State.CONNECTING =>
|
||||
fab.setBackgroundTintList(greyTint);
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
fab.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
setPreferenceEnabled(enabled = false)
|
||||
case State.CONNECTED =>
|
||||
fab.setBackgroundTintList(greenTint);
|
||||
fab.setBackgroundTintList(greenTint)
|
||||
if (state == State.CONNECTING) {
|
||||
fabProgressCircle.beginFinalAnimation()
|
||||
} else {
|
||||
@@ -1042,7 +719,7 @@ class Shadowsocks
|
||||
changeSwitch(checked = true)
|
||||
setPreferenceEnabled(enabled = false)
|
||||
case State.STOPPED =>
|
||||
fab.setBackgroundTintList(greyTint);
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
handler.postDelayed(() => fabProgressCircle.hide(), 1000)
|
||||
fab.setEnabled(true)
|
||||
changeSwitch(checked = false)
|
||||
@@ -1050,7 +727,7 @@ class Shadowsocks
|
||||
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show
|
||||
setPreferenceEnabled(enabled = true)
|
||||
case State.STOPPING =>
|
||||
fab.setBackgroundTintList(greyTint);
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
fab.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
@@ -1059,12 +736,4 @@ class Shadowsocks
|
||||
state = s
|
||||
})
|
||||
}
|
||||
|
||||
class PreferenceBroadcastReceiver extends BroadcastReceiver {
|
||||
override def onReceive(context: Context, intent: Intent) {
|
||||
currentProfile = profileManager.save()
|
||||
menuAdapter.updateList(getMenuList, currentProfile.id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,19 +43,47 @@ import java.util
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import android.app.Application
|
||||
import com.github.shadowsocks.database.DBHelper
|
||||
import com.github.shadowsocks.utils.Utils
|
||||
import com.google.android.gms.analytics.GoogleAnalytics
|
||||
import android.content.pm.PackageManager
|
||||
import android.preference.PreferenceManager
|
||||
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
import com.google.android.gms.analytics.{HitBuilders, GoogleAnalytics}
|
||||
import com.google.android.gms.common.api.ResultCallback
|
||||
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
|
||||
|
||||
object ShadowsocksApplication {
|
||||
var instance: ShadowsocksApplication = _
|
||||
lazy val dbHelper = new DBHelper(instance)
|
||||
final val SIG_FUNC = "getSignature"
|
||||
var containerHolder: ContainerHolder = _
|
||||
lazy val tracker = GoogleAnalytics.getInstance(instance).newTracker(R.xml.tracker)
|
||||
lazy val settings = PreferenceManager.getDefaultSharedPreferences(instance)
|
||||
lazy val profileManager = new ProfileManager(settings, dbHelper)
|
||||
|
||||
def getVersionName = try {
|
||||
instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName
|
||||
} catch {
|
||||
case _: PackageManager.NameNotFoundException => "Package name not found"
|
||||
case _: Throwable => null
|
||||
}
|
||||
|
||||
// send event
|
||||
def track(category: String, action: String) = tracker.send(new HitBuilders.EventBuilder()
|
||||
.setAction(action)
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
|
||||
def profileId = settings.getInt(Key.profileId, -1)
|
||||
def profileId(i: Int) = settings.edit.putInt(Key.profileId, i).apply
|
||||
def proxy = settings.getString(Key.proxy, "")
|
||||
def currentProfile = profileManager.getProfile(profileId)
|
||||
}
|
||||
|
||||
class ShadowsocksApplication extends Application {
|
||||
lazy val dbHelper = new DBHelper(this)
|
||||
lazy val SIG_FUNC = "getSignature"
|
||||
var containerHolder: ContainerHolder = null
|
||||
lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
|
||||
import ShadowsocksApplication._
|
||||
|
||||
override def onCreate() {
|
||||
ShadowsocksApplication.instance = this
|
||||
val tm = TagManager.getInstance(this)
|
||||
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
|
||||
val callback = new ResultCallback[ContainerHolder] {
|
||||
|
||||
@@ -40,22 +40,20 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.io.File
|
||||
import java.lang.reflect.{InvocationTargetException, Method}
|
||||
import java.net.{Inet6Address, InetAddress}
|
||||
import java.util.Locale
|
||||
|
||||
import android.app._
|
||||
import android.content._
|
||||
import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.{Network, ConnectivityManager}
|
||||
import android.net.{ConnectivityManager, Network}
|
||||
import android.os._
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.{SparseArray, Log}
|
||||
import android.util.{Log, SparseArray}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.google.android.gms.analytics.HitBuilders
|
||||
|
||||
import scala.collection._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
@@ -77,8 +75,6 @@ class ShadowsocksNatService extends Service with BaseService {
|
||||
var apps: Array[ProxiedApp] = null
|
||||
val myUid = Process.myUid()
|
||||
|
||||
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
|
||||
|
||||
private val dnsAddressCache = new SparseArray[String]
|
||||
|
||||
def getNetId(network: Network): Int = {
|
||||
@@ -254,8 +250,8 @@ class ShadowsocksNatService extends Service with BaseService {
|
||||
def startDnsDaemon() {
|
||||
|
||||
val conf = if (config.route == Route.BYPASS_CHN) {
|
||||
val reject = ConfigUtils.getRejectList(getContext, application)
|
||||
val blackList = ConfigUtils.getBlackList(getContext, application)
|
||||
val reject = ConfigUtils.getRejectList(getContext)
|
||||
val blackList = ConfigUtils.getBlackList(getContext)
|
||||
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "127.0.0.1", 8153,
|
||||
Path.BASE + "pdnsd-nat.pid", reject, blackList, 8163, "")
|
||||
} else {
|
||||
@@ -457,19 +453,14 @@ class ShadowsocksNatService extends Service with BaseService {
|
||||
registerReceiver(lockReceiver, screenFilter)
|
||||
}
|
||||
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(TAG)
|
||||
.setAction("start")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
ShadowsocksApplication.track(TAG, "start")
|
||||
|
||||
changeState(State.CONNECTING)
|
||||
|
||||
Future {
|
||||
|
||||
if (config.proxy == "198.199.101.152") {
|
||||
val holder = application.containerHolder
|
||||
val holder = ShadowsocksApplication.containerHolder
|
||||
try {
|
||||
config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
|
||||
} catch {
|
||||
@@ -531,12 +522,7 @@ class ShadowsocksNatService extends Service with BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(TAG)
|
||||
.setAction("stop")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
ShadowsocksApplication.track(TAG, "stop")
|
||||
|
||||
// reset NAT
|
||||
killProcesses()
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceFragment
|
||||
import android.preference.{Preference, PreferenceFragment}
|
||||
|
||||
// TODO: Move related logic here
|
||||
class ShadowsocksSettings extends PreferenceFragment {
|
||||
private lazy val activity = getActivity.asInstanceOf[Shadowsocks]
|
||||
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.pref_all)
|
||||
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
|
||||
ShadowsocksApplication.track(Shadowsocks.TAG, "reset")
|
||||
activity.recovery()
|
||||
true
|
||||
})
|
||||
findPreference("flush_dnscache").setOnPreferenceClickListener((preference: Preference) => {
|
||||
ShadowsocksApplication.track(Shadowsocks.TAG, "flush_dnscache")
|
||||
activity.flushDnsCache()
|
||||
true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.google.android.gms.analytics.HitBuilders
|
||||
import org.apache.commons.net.util.SubnetUtils
|
||||
|
||||
import scala.collection.mutable
|
||||
@@ -62,8 +61,6 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
|
||||
class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
|
||||
val TAG = "ShadowsocksVpnService"
|
||||
val VPN_MTU = 1500
|
||||
val PRIVATE_VLAN = "26.26.26.%s"
|
||||
@@ -152,12 +149,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
// channge the state
|
||||
changeState(State.STOPPING)
|
||||
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(TAG)
|
||||
.setAction("stop")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
ShadowsocksApplication.track(TAG, "stop")
|
||||
|
||||
// reset VPN
|
||||
killProcesses()
|
||||
@@ -231,18 +223,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
return
|
||||
}
|
||||
|
||||
// send event
|
||||
application.tracker.send(new HitBuilders.EventBuilder()
|
||||
.setCategory(TAG)
|
||||
.setAction("start")
|
||||
.setLabel(getVersionName)
|
||||
.build())
|
||||
ShadowsocksApplication.track(TAG, "start")
|
||||
|
||||
changeState(State.CONNECTING)
|
||||
|
||||
Future {
|
||||
if (config.proxy == "198.199.101.152") {
|
||||
val holder = getApplication.asInstanceOf[ShadowsocksApplication].containerHolder
|
||||
val holder = ShadowsocksApplication.containerHolder
|
||||
try {
|
||||
config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
|
||||
} catch {
|
||||
@@ -371,8 +358,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
val ipv6 = if (config.isIpv6) "" else "reject = ::/0;"
|
||||
val conf = {
|
||||
if (config.route == Route.BYPASS_CHN) {
|
||||
val reject = ConfigUtils.getRejectList(getContext, application)
|
||||
val blackList = ConfigUtils.getBlackList(getContext, application)
|
||||
val reject = ConfigUtils.getRejectList(getContext)
|
||||
val blackList = ConfigUtils.getBlackList(getContext)
|
||||
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153,
|
||||
Path.BASE + "pdnsd-vpn.pid", reject, blackList, 8163, ipv6)
|
||||
} else {
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Shadowsocks - A shadowsocks client for Android
|
||||
* Copyright (C) 2014 <max.c.lv@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* ___====-_ _-====___
|
||||
* _--^^^#####// \\#####^^^--_
|
||||
* _-^##########// ( ) \\##########^-_
|
||||
* -############// |\^^/| \\############-
|
||||
* _/############// (@::@) \\############\_
|
||||
* /#############(( \\// ))#############\
|
||||
* -###############\\ (oo) //###############-
|
||||
* -#################\\ / VV \ //#################-
|
||||
* -###################\\/ \//###################-
|
||||
* _#/|##########/\######( /\ )######/\##########|\#_
|
||||
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
|
||||
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
|
||||
* ` ` ` ` / | | | | \ ' ' ' '
|
||||
* ( | | | | )
|
||||
* __\ | | | | /__
|
||||
* (vvv(VVV)(VVV)vvv)
|
||||
*
|
||||
* HERE BE DRAGONS
|
||||
*
|
||||
*/
|
||||
|
||||
package com.github.shadowsocks.database
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.{LayoutInflater, View, ViewGroup}
|
||||
import android.widget.{BaseAdapter, TextView}
|
||||
import com.github.shadowsocks.R
|
||||
|
||||
abstract class Item {
|
||||
def id: Int
|
||||
def title: String
|
||||
def click: Int => Unit
|
||||
def longClick: Int => Boolean
|
||||
}
|
||||
|
||||
case class IconItem(id: Int, title: String, iconRes: Int,
|
||||
click: Int => Unit, longClick: Int => Boolean = _ => false)
|
||||
extends Item
|
||||
|
||||
case class DrawableItem(id: Int, title: String, iconRes: Drawable,
|
||||
click: Int => Unit, longClick: Int => Boolean = _ => false)
|
||||
extends Item
|
||||
|
||||
case class Category(title: String)
|
||||
|
||||
object MenuAdapter {
|
||||
|
||||
trait MenuListener {
|
||||
def onActiveViewChanged(v: View, pos: Int)
|
||||
}
|
||||
|
||||
val UNDEFINED = -1
|
||||
val CATEGORY = 0
|
||||
val ITEM = 1
|
||||
}
|
||||
|
||||
class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter {
|
||||
|
||||
def setListener(listener: MenuAdapter.MenuListener) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
def setActiveId(activeId: Int) {
|
||||
this.activeId = activeId
|
||||
}
|
||||
|
||||
@Override def getCount: Int = {
|
||||
items.size
|
||||
}
|
||||
|
||||
@Override def getItem(position: Int): Object = {
|
||||
items(position).asInstanceOf[Object]
|
||||
}
|
||||
|
||||
@Override def getItemId(position: Int): Long = {
|
||||
position
|
||||
}
|
||||
|
||||
@Override override def getItemViewType(position: Int): Int = {
|
||||
getItem(position) match {
|
||||
case Category => MenuAdapter.CATEGORY
|
||||
case IconItem => MenuAdapter.ITEM
|
||||
case DrawableItem => MenuAdapter.ITEM
|
||||
case _ => MenuAdapter.UNDEFINED
|
||||
}
|
||||
}
|
||||
|
||||
@Override override def getViewTypeCount: Int = {
|
||||
3
|
||||
}
|
||||
|
||||
@Override override def isEnabled(position: Int): Boolean = {
|
||||
getItemViewType(position) != MenuAdapter.UNDEFINED
|
||||
}
|
||||
|
||||
@Override override def areAllItemsEnabled: Boolean = {
|
||||
false
|
||||
}
|
||||
|
||||
@Override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
|
||||
var v: View = convertView
|
||||
val item = getItem(position)
|
||||
item match {
|
||||
case value: Category =>
|
||||
if (v == null) {
|
||||
v = LayoutInflater.from(context).inflate(R.layout.menu_row_category, parent, false)
|
||||
}
|
||||
v.asInstanceOf[TextView].setText(value.title)
|
||||
case value: Item =>
|
||||
if (v == null) {
|
||||
v = LayoutInflater.from(context).inflate(R.layout.menu_row_item, parent, false)
|
||||
}
|
||||
val tv: TextView = v.asInstanceOf[TextView]
|
||||
tv.setText(value.title)
|
||||
|
||||
value match {
|
||||
case iconItem: IconItem =>
|
||||
if (iconItem.iconRes != -1) {
|
||||
tv.setCompoundDrawablesWithIntrinsicBounds(iconItem.iconRes, 0, 0, 0)
|
||||
}
|
||||
case drawableItem: DrawableItem =>
|
||||
if (drawableItem.iconRes != null) {
|
||||
tv.setCompoundDrawablesWithIntrinsicBounds(drawableItem.iconRes, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
tv.setOnClickListener((view: View) => {
|
||||
val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
|
||||
item.click(item.id)
|
||||
})
|
||||
tv.setOnLongClickListener((view: View) => {
|
||||
val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
|
||||
item.longClick(item.id)
|
||||
})
|
||||
|
||||
v.setTag(R.id.mdItem, value)
|
||||
|
||||
if (value.id == activeId) {
|
||||
if (listener != null) listener.onActiveViewChanged(v, position)
|
||||
}
|
||||
|
||||
case _ =>
|
||||
}
|
||||
v.setTag(R.id.mdActiveViewPosition, position)
|
||||
|
||||
|
||||
v
|
||||
}
|
||||
|
||||
def updateList(list: List[Any], activeId: Int) {
|
||||
items = list
|
||||
this.activeId = activeId
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private var listener: MenuAdapter.MenuListener = null
|
||||
private var activeId: Int = -1
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Shadowsocks - A shadowsocks client for Android
|
||||
* Copyright (C) 2013 <max.c.lv@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*
|
||||
* ___====-_ _-====___
|
||||
* _--^^^#####// \\#####^^^--_
|
||||
* _-^##########// ( ) \\##########^-_
|
||||
* -############// |\^^/| \\############-
|
||||
* _/############// (@::@) \\############\_
|
||||
* /#############(( \\// ))#############\
|
||||
* -###############\\ (oo) //###############-
|
||||
* -#################\\ / VV \ //#################-
|
||||
* -###################\\/ \//###################-
|
||||
* _#/|##########/\######( /\ )######/\##########|\#_
|
||||
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
|
||||
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
|
||||
* ` ` ` ` / | | | | \ ' ' ' '
|
||||
* ( | | | | )
|
||||
* __\ | | | | /__
|
||||
* (vvv(VVV)(VVV)vvv)
|
||||
*
|
||||
* HERE BE DRAGONS
|
||||
*
|
||||
*/
|
||||
|
||||
package com.github.shadowsocks.preferences
|
||||
|
||||
import android.content.{Intent, Context}
|
||||
import android.preference.EditTextPreference
|
||||
import android.util.AttributeSet
|
||||
import com.github.shadowsocks.utils.Action
|
||||
|
||||
class ProfileEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
|
||||
extends EditTextPreference(context, attrs, defStyle) {
|
||||
|
||||
def this(context: Context, attrs: AttributeSet) = {
|
||||
this(context, attrs, android.R.attr.editTextPreferenceStyle)
|
||||
mDefaultSummary = getSummary
|
||||
}
|
||||
|
||||
override def setText(text: String) {
|
||||
super.setText(text)
|
||||
setSummary(text)
|
||||
}
|
||||
|
||||
override def setSummary(summary: CharSequence) {
|
||||
if (summary.toString.isEmpty) {
|
||||
super.setSummary(mDefaultSummary)
|
||||
} else {
|
||||
super.setSummary(summary)
|
||||
}
|
||||
context.sendBroadcast(new Intent(Action.UPDATE_PREFS))
|
||||
}
|
||||
|
||||
def resetSummary(summary: CharSequence) {
|
||||
if (summary.toString.isEmpty) {
|
||||
super.setSummary(mDefaultSummary)
|
||||
} else {
|
||||
super.setSummary(summary)
|
||||
}
|
||||
}
|
||||
|
||||
private var mDefaultSummary: CharSequence = getSummary
|
||||
}
|
||||
@@ -198,14 +198,14 @@ object ConfigUtils {
|
||||
}
|
||||
|
||||
def refresh(context: Context) {
|
||||
val holder = context.getApplicationContext.asInstanceOf[ShadowsocksApplication].containerHolder
|
||||
val holder = ShadowsocksApplication.containerHolder
|
||||
if (holder != null) holder.refresh()
|
||||
}
|
||||
|
||||
def getRejectList(context: Context, app: ShadowsocksApplication): String = {
|
||||
def getRejectList(context: Context): String = {
|
||||
val default = context.getString(R.string.reject)
|
||||
try {
|
||||
val container = app.containerHolder.getContainer
|
||||
val container = ShadowsocksApplication.containerHolder.getContainer
|
||||
val update = container.getString("reject")
|
||||
if (update == null || update.isEmpty) default else update
|
||||
} catch {
|
||||
@@ -213,10 +213,10 @@ object ConfigUtils {
|
||||
}
|
||||
}
|
||||
|
||||
def getBlackList(context: Context, app: ShadowsocksApplication): String = {
|
||||
def getBlackList(context: Context): String = {
|
||||
val default = context.getString(R.string.black_list)
|
||||
try {
|
||||
val container = app.containerHolder.getContainer
|
||||
val container = ShadowsocksApplication.containerHolder.getContainer
|
||||
val update = container.getString("black_list")
|
||||
if (update == null || update.isEmpty) default else update
|
||||
} catch {
|
||||
|
||||
@@ -111,8 +111,6 @@ object State {
|
||||
object Action {
|
||||
val SERVICE = "com.github.shadowsocks.SERVICE"
|
||||
val CLOSE = "com.github.shadowsocks.CLOSE"
|
||||
val UPDATE_FRAGMENT = "com.github.shadowsocks.ACTION_UPDATE_FRAGMENT"
|
||||
val UPDATE_PREFS = "com.github.shadowsocks.ACTION_UPDATE_PREFS"
|
||||
}
|
||||
|
||||
object Route {
|
||||
|
||||
@@ -92,6 +92,9 @@ object Utils {
|
||||
new String(Base64.encode(mdg.digest, 0))
|
||||
}
|
||||
|
||||
def dpToPx(context: Context, dp: Int): Int =
|
||||
Math.round(dp * (context.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
|
||||
|
||||
/*
|
||||
* round or floor depending on whether you are using offsets(floor) or
|
||||
* widths(round)
|
||||
|
||||
Reference in New Issue
Block a user