Commit 51e155c9 authored by Mygod's avatar Mygod

Add more material design (3)

parent 1f695b9c
......@@ -29,13 +29,10 @@ libraryDependencies ++= Seq(
"com.github.kevinsawicki" % "http-request" % "6.0",
"commons-net" % "commons-net" % "3.3",
"com.google.zxing" % "android-integration" % "3.2.1",
"com.joanzapata.android" % "android-iconify" % "1.0.11",
"net.glxn.qrgen" % "android" % "2.0",
"net.simonvt.menudrawer" % "menudrawer" % "3.0.6",
"com.google.android.gms" % "play-services-base" % "8.1.0",
"com.google.android.gms" % "play-services-ads" % "8.1.0",
"com.google.android.gms" % "play-services-analytics" % "8.1.0",
"com.android.support" % "support-v4" % "23.1.0",
"com.android.support" % "design" % "23.1.0",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.nostra13.universalimageloader" % "universal-image-loader" % "1.9.4",
......@@ -46,13 +43,9 @@ libraryDependencies ++= Seq(
proguardOptions in Android ++= Seq("-keep class android.support.v4.app.** { *; }",
"-keep interface android.support.v4.app.** { *; }",
"-keep class android.support.v7.widget.Toolbar { <init>(...); }",
"-keep class com.actionbarsherlock.** { *; }",
"-keep interface com.actionbarsherlock.** { *; }",
"-keep class org.jraf.android.backport.** { *; }",
"-keep class com.github.shadowsocks.** { *; }",
"-keep class * extends com.j256.ormlite.** { *; }",
"-keep class com.joanzapata.** { *; }",
"-keepattributes *Annotation*",
"-dontwarn com.google.android.gms.internal.zzhu",
"-dontwarn org.xbill.**",
"-dontwarn com.actionbarsherlock.**")
"-dontwarn org.xbill.**")
......@@ -22,7 +22,8 @@
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:backupAgent=".ShadowsocksBackupAgent"
android:label="@string/app_name">
android:label="@string/app_name"
android:theme="@style/Theme.Material">
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
......@@ -36,7 +37,6 @@
<activity
android:name=".Shadowsocks"
android:label="@string/app_name"
android:theme="@style/Theme.Shadowsocks"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
......@@ -62,11 +62,22 @@
android:launchMode="singleTask">
</activity>
<activity
android:name=".ProfileManagerActivity"
android:label="@string/app_name"
android:parentActivityName=".Shadowsocks"
android:exported="false">
<intent-filter>
<action android:name="com.github.shadowsocks.ProfileManagerActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".AppManager"
android:theme="@style/Theme.Material"
android:label="@string/app_name"
android:parentActivityName=".Shadowsocks">
android:parentActivityName=".Shadowsocks"
android:exported="false">
<intent-filter>
<action android:name="com.github.shadowsocks.AppManager"/>
<category android:name="android.intent.category.DEFAULT"/>
......
Subproject commit 095aa7490cb39d513180d1f756e6751107511f98
Subproject commit 890e880dc61ad4f2e9a340a78c797c4aabee6429
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/toolbar_light_dark" />
<android.support.v7.widget.RecyclerView android:id="@+id/profilesList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on: https://github.com/android/platform_frameworks_base/blob/master/core/res/res/layout/select_dialog_singlechoice_material.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingStart="20dp"
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingLeft="20dp"
android:paddingRight="?attr/dialogPreferredPadding">
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorAlertDialogListItem"
android:gravity="center_vertical"
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
android:drawableLeft="?android:attr/listChoiceIndicatorSingle"
android:drawablePadding="20dp"
android:maxLines="4"
android:scrollHorizontally="false" />
<!-- TODO: Replace this view with an ImageView -->
<ImageView android:id="@+id/qrcode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@android:drawable/ic_menu_add"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/qrcode"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MenuDrawer.Widget.Category" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MenuDrawer.Widget.Title" />
\ No newline at end of file
......@@ -7,6 +7,5 @@
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:titleTextAppearance="@style/Toolbar.TitleText"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:id="@+id/toolbar" />
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Todo: replace this placeholder icon with a material one -->
<item android:title="@string/add_profile"
android:icon="@android:drawable/ic_menu_add"
android:alphabeticShortcut="a"
android:numericShortcut="0"
app:showAsAction="always">
<menu>
<item android:id="@+id/scan_qr_code"
android:title="@string/add_profile_methods_scan_qr_code"
android:alphabeticShortcut="s"
android:numericShortcut="1"/>
<item android:id="@+id/manual_settings"
android:title="@string/add_profile_methods_manual_settings"
android:alphabeticShortcut="m"
android:numericShortcut="2"/>
</menu>
</item>
</menu>
......@@ -3,9 +3,6 @@
<resources>
<string name="app_name">影梭</string>
<string name="screen_name">shadowsocks</string>
<string name="function_cat">服务控制</string>
<string name="service_controller">代理开关</string>
<string name="service_summary">启动/关闭后台服务</string>
<string name="proxy_cat">服务器设置</string>
<string name="profile">配置文件</string>
<string name="profile_summary">配置文件名称</string>
......@@ -39,26 +36,18 @@
<string name="traffic_stat_summary">在通知栏上显示当前上下行速率</string>
<string name="forward_success">后台服务已开始运行。</string>
<string name="service_running">已连接到「%s」</string>
<string name="service_status" formatted="false">当前速率:%d KB/s</string>
<string name="forward_fail">后台服务启动失败。</string>
<string name="service_stopped">后台服务已停止。</string>
<string name="forward_stop">后台服务已停止。</string>
<string name="service_failed">无法连接远程服务器</string>
<string name="stop">停止服务</string>
<string name="stopping">正在关闭...</string>
<string name="vpn_error">后台服务启动失败:%s</string>
<string name="ok_iknow">确定</string>
<string name="close">关闭</string>
<string name="port_alert">本地端口号应大于1024</string>
<string name="port_empty">端口号不能为空</string>
<string name="proxy_empty">代理服务器地址不能为空</string>
<string name="crash_alert">检测到意外退出,状态已被重置。</string>
<string name="connect">连接</string>
<string name="initializing">正在初始化...</string>
<string name="recovering">重置中...</string>
<string name="loading">正在加载...</string>
<string name="yes"></string>
<string name="no"></string>
<string name="profiles">配置文件</string>
<string name="settings">设置选项</string>
<string name="remove_profile">删除此配置文件「%s」?</string>
......@@ -84,10 +73,8 @@
<!-- profile -->
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
<string-array name="add_profile_methods">
<item>扫描二维码</item>
<item>手动设置</item>
</string-array>
<string name="add_profile_methods_scan_qr_code">扫描二维码</string>
<string name="add_profile_methods_manual_settings">手动设置</string>
<string-array name="route_entry">
<item>全局</item>
......@@ -101,6 +88,8 @@
<string name="action_export_err">导出失败</string>
<string name="action_import_msg">导入成功</string>
<string name="action_import_err">导入失败</string>
<string name="error">错误</string>
<string name="removed">已删除</string>
<string name="undo">撤销</string>
</resources>
......@@ -4,15 +4,14 @@
<string name="app_name">Shadowsocks</string>
<string name="screen_name">shadowsocks</string>
<!-- function category -->
<string name="function_cat">Service Controller</string>
<string name="service_controller">Proxy Switch</string>
<string name="service_summary">Enable / Disable Proxy</string>
<!-- global category -->
<string name="global_cat">Global Settings</string>
<string name="profile">Profile</string>
<string name="profile_summary">Switch to another profile</string>
<!-- proxy category -->
<string name="proxy_cat">Server Settings</string>
<string name="profile">Profile</string>
<string name="profile_summary">Profile name</string>
<string name="profile_name">Profile name</string>
<string name="proxy">Server</string>
<string name="proxy_summary">Hostname or IP of the remote server</string>
<string name="remote_port">Remote Port</string>
......@@ -54,26 +53,18 @@
<!-- notification category -->
<string name="forward_success">Shadowsocks started.</string>
<string name="service_running">Connected to "%s"</string>
<string name="service_status" formatted="false">Current Rate: %d KB/s</string>
<string name="forward_fail">Shadowsocks failed to start.</string>
<string name="service_stopped">Shadowsocks stopped.</string>
<string name="forward_stop">Shadowsocks stopped.</string>
<string name="service_failed">Failed to connect the remote server</string>
<string name="stop">Stop the service</string>
<string name="stopping">Shutting down...</string>
<string name="vpn_error">%s</string>
<string name="enable_nat">Turn on NAT mode.</string>
<string name="disable_nat">Turn off NAT mode.</string>
<string name="error">ERROR</string>
<!-- alert category -->
<string name="ok_iknow">OK</string>
<string name="close">Close</string>
<string name="port_alert">The local port number should be greater than 1024</string>
<string name="port_empty">Port should not be empty</string>
<string name="proxy_empty">Proxy should not be empty</string>
<string name="crash_alert">An unexpected exit detected, the context has been reset.
</string>
<string name="connect">Connect</string>
<string name="initializing">Initializing…</string>
<string name="recovering">Resetting…</string>
......@@ -101,9 +92,9 @@
<!-- profile -->
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
<string-array name="add_profile_methods">
<item>Scan QR code</item>
<item>Manual Settings</item>
</string-array>
<string name="add_profile_methods_scan_qr_code">Scan QR code</string>
<string name="add_profile_methods_manual_settings">Manual Settings</string>
<string name="removed">Removed</string>
<string name="undo">Undo</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!--
Zirco Browser for Android
Copyright (C) 2010 - 2011 J. Devauchelle and contributors.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation.
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.
-->
<resources>
<style name="Toolbar.TitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
<style name="Toolbar.Logo" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:textSize">24sp</item>
</style>
<style name="Theme.Material" parent="Theme.AppCompat.Light.NoActionBar">
......@@ -51,45 +37,4 @@
<item name="android:windowIsTranslucent">true</item>
</style>
<!-- Variation on the Light theme that turns off the title -->
<style name="Theme.Shadowsocks" parent="Theme.Material">
<item name="menuDrawerStyle">@style/MenuDrawerStyle</item>
</style>
<style name="MenuDrawer"/>
<style name="MenuDrawer.Widget"/>
<style name="MenuDrawer.Widget.Category">
<item name="android:textStyle">bold</item>
<item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:background">@drawable/md__category_background</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
</style>
<style name="MenuDrawer.Widget.Title">
<item name="android:background">?android:attr/selectableItemBackground</item>
<item name="android:textAppearance">?android:attr/textAppearance</item>
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="android:textSize">18sp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">32dp</item>
<item name="android:paddingTop">8dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:drawablePadding">16dp</item>
<item name="android:gravity">center_vertical</item>
</style>
<style name="MenuDrawerStyle" parent="Widget.MenuDrawer">
<item name="mdActiveIndicator">@drawable/menu_arrow</item>
<item name="mdMenuSize">250dp</item>
</style>
</resources>
......@@ -2,50 +2,58 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<PreferenceCategory android:title="@string/global_cat">
<Preference android:summary="@string/profile_summary" android:title="@string/profile">
<intent android:action="com.github.shadowsocks.ProfileManagerActivity"/>
</Preference>
<!-- TODO: VPN/NAT SwitchPreference -->
<SwitchPreference
android:key="isAutoConnect"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
<Preference android:key="recovery" android:title="@string/recovery"/>
<Preference android:key="flush_dnscache" android:title="@string/flush_dnscache"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/proxy_cat">
<com.github.shadowsocks.preferences.ProfileEditTextPreference
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="Default"
android:key="profileName"
android:summary="@string/profile_summary"
android:title="@string/profile">
</com.github.shadowsocks.preferences.ProfileEditTextPreference>
android:title="@string/profile_name"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="198.199.101.152"
android:key="proxy"
android:summary="@string/proxy_summary"
android:title="@string/proxy">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
android:title="@string/proxy"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:inputType="number"
android:defaultValue="443"
android:key="remotePort"
android:summary="@string/remote_port_summary"
android:title="@string/remote_port">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
android:title="@string/remote_port"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:inputType="number"
android:defaultValue="1080"
android:key="port"
android:summary="@string/port_summary"
android:title="@string/port">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
android:title="@string/port"/>
<com.github.shadowsocks.preferences.PasswordEditTextPreference
android:inputType="textPassword"
android:defaultValue="u1rRWTssNv0p"
android:key="sitekey"
android:summary="@string/sitekey_summary"
android:title="@string/sitekey">
</com.github.shadowsocks.preferences.PasswordEditTextPreference>
android:title="@string/sitekey"/>
<com.github.shadowsocks.preferences.DropDownPreference
android:defaultValue="rc4"
android:key="encMethod"
app:entries="@array/enc_method_entry"
app:entryValues="@array/enc_method_value"
app:summary="%s"
android:title="@string/enc_method">
</com.github.shadowsocks.preferences.DropDownPreference>
android:title="@string/enc_method"/>
</PreferenceCategory>
......@@ -58,21 +66,18 @@
app:entries="@array/route_entry"
app:entryValues="@array/route_value"
app:summary="%s"
android:title="@string/route_list">
</com.github.shadowsocks.preferences.DropDownPreference>
android:title="@string/route_list"/>
<SwitchPreference
android:key="isIpv6"
android:defaultValue="false"
android:summary="@string/ipv6_summary"
android:title="@string/ipv6">
</SwitchPreference>
android:title="@string/ipv6"/>
<SwitchPreference
android:defaultValue="true"
android:key="isGlobalProxy"
android:disableDependentsState="true"
android:summary="@string/auto_set_proxy_summary"
android:title="@string/auto_set_proxy">
</SwitchPreference>
android:title="@string/auto_set_proxy"/>
<Preference
android:key="proxyedApps"
android:dependency="isGlobalProxy"
......@@ -84,19 +89,12 @@
android:key="isUdpDns"
android:defaultValue="false"
android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns">
</SwitchPreference>
android:title="@string/udp_dns"/>
<SwitchPreference
android:key="isAuth"
android:defaultValue="false"
android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth">
</SwitchPreference>
<SwitchPreference
android:key="isAutoConnect"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect">
</SwitchPreference>
android:title="@string/onetime_auth"/>
</PreferenceCategory>
......
......@@ -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 android.widget._
import com.github.shadowsocks.utils.{Key, Scheme, Utils}
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import java.io.{ByteArrayOutputStream, ByteArrayInputStream, InputStream}
import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, ImageLoaderConfiguration}
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
import com.github.shadowsocks.utils.{Utils, Scheme, Key}
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)
......
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)
val h = showProgress(R.string.loading)
handler.postDelayed(() => {
currentProfile = {
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse currentProfile
}
menuAdapter.updateList(getMenuList, currentProfile.id)
updatePreferenceScreen()
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
}
h.sendEmptyMessage(0)
}, 600)
updatePreferenceScreen()
}
def addProfile(profile: Profile) {
drawer.closeMenu(true)
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)
}
currentProfile = profile
ShadowsocksApplication.profileManager.createOrUpdateProfile(currentProfile)
ShadowsocksApplication.profileManager.reload(currentProfile.id)
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()
......
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 {
......
/*
* 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
}
/*
* 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)
......
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