Move app to mobile subproject
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
/*******************************************************************************/
|
||||
/* */
|
||||
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
|
||||
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
|
||||
/* */
|
||||
/* This program is free software: you can redistribute it and/or modify */
|
||||
/* it under the terms of the GNU General Public License as published by */
|
||||
/* the Free Software Foundation, either version 3 of the License, or */
|
||||
/* (at your option) any later version. */
|
||||
/* */
|
||||
/* This program is distributed in the hope that it will be useful, */
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
|
||||
/* GNU General Public License for more details. */
|
||||
/* */
|
||||
/* You should have received a copy of the GNU General Public License */
|
||||
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
/* */
|
||||
/*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.database
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
|
||||
import com.j256.ormlite.dao.Dao
|
||||
import com.j256.ormlite.support.ConnectionSource
|
||||
import com.j256.ormlite.table.TableUtils
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
|
||||
object DBHelper {
|
||||
final val PROFILE = "profile.db"
|
||||
private var apps: mutable.Buffer[ApplicationInfo] = _
|
||||
|
||||
def isAllDigits(x: String): Boolean = !x.isEmpty && (x forall Character.isDigit)
|
||||
|
||||
def updateProxiedApps(context: Context, old: String): String = {
|
||||
synchronized(if (apps == null) apps = context.getPackageManager.getInstalledApplications(0).asScala)
|
||||
val uidSet = old.split('|').filter(isAllDigits).map(_.toInt).toSet
|
||||
apps.filter(ai => uidSet.contains(ai.uid)).map(_.packageName).mkString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
class DBHelper(val context: Context)
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 21) {
|
||||
import DBHelper._
|
||||
|
||||
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
|
||||
|
||||
def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
|
||||
TableUtils.createTable(connectionSource, classOf[Profile])
|
||||
}
|
||||
|
||||
def recreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
|
||||
TableUtils.dropTable(connectionSource, classOf[Profile], true)
|
||||
onCreate(database, connectionSource)
|
||||
}
|
||||
|
||||
def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int,
|
||||
newVersion: Int) {
|
||||
if (oldVersion != newVersion) {
|
||||
if (oldVersion < 7) {
|
||||
recreate(database, connectionSource)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (oldVersion < 8) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
|
||||
} else if (oldVersion < 19) {
|
||||
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
TableUtils.createTable(connectionSource, classOf[Profile])
|
||||
profileDao.executeRawNoArgs(
|
||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
|
||||
" udpdns, auth, ipv6, individual) " +
|
||||
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," +
|
||||
" ipv6, individual FROM `tmp`;")
|
||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||
profileDao.executeRawNoArgs("COMMIT;")
|
||||
} else if (oldVersion < 13) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
|
||||
}
|
||||
|
||||
if (oldVersion < 15) {
|
||||
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
|
||||
var i = 0
|
||||
for (profile <- profileDao.queryForAll.asScala) {
|
||||
if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual)
|
||||
profile.userOrder = i
|
||||
profileDao.update(profile)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
if (oldVersion < 16) {
|
||||
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
|
||||
}
|
||||
|
||||
if (oldVersion < 17) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcp SMALLINT;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpcli VARCHAR DEFAULT " +
|
||||
"'--crypt none --mode normal --mtu 1200 --nocomp --dscp 46 --parityshard 0';")
|
||||
} else if (oldVersion < 20) {
|
||||
profileDao.executeRawNoArgs("UPDATE `profile` SET kcpcli = '--crypt none --mode normal --mtu 1200 --nocomp " +
|
||||
"--dscp 46 --parityshard 0' WHERE kcpcli IS NULL;")
|
||||
}
|
||||
|
||||
if (oldVersion < 18) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpPort INTEGER DEFAULT 8399;")
|
||||
} else if (oldVersion < 19) {
|
||||
profileDao.executeRawNoArgs("UPDATE `profile` SET kcpPort = 8399 WHERE kcpPort = 0;")
|
||||
}
|
||||
|
||||
if (oldVersion < 21) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
app.track(ex)
|
||||
recreate(database, connectionSource)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*******************************************************************************/
|
||||
/* */
|
||||
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
|
||||
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
|
||||
/* */
|
||||
/* This program is free software: you can redistribute it and/or modify */
|
||||
/* it under the terms of the GNU General Public License as published by */
|
||||
/* the Free Software Foundation, either version 3 of the License, or */
|
||||
/* (at your option) any later version. */
|
||||
/* */
|
||||
/* This program is distributed in the hope that it will be useful, */
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
|
||||
/* GNU General Public License for more details. */
|
||||
/* */
|
||||
/* You should have received a copy of the GNU General Public License */
|
||||
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
/* */
|
||||
/*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.database
|
||||
|
||||
import java.net.URLEncoder
|
||||
import java.util.Locale
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Binder
|
||||
import android.text.TextUtils
|
||||
import android.util.Base64
|
||||
import com.github.shadowsocks.utils.Key
|
||||
import com.j256.ormlite.field.{DataType, DatabaseField}
|
||||
|
||||
class Profile {
|
||||
@DatabaseField(generatedId = true)
|
||||
var id: Int = _
|
||||
|
||||
@DatabaseField
|
||||
var name: String = ""
|
||||
|
||||
@DatabaseField
|
||||
var host: String = "198.199.101.152"
|
||||
|
||||
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
|
||||
@DatabaseField
|
||||
var localPort: Int = 1080 + Binder.getCallingUserHandle.hashCode
|
||||
|
||||
@DatabaseField
|
||||
var remotePort: Int = 8388
|
||||
|
||||
@DatabaseField
|
||||
var password: String = "u1rRWTssNv0p"
|
||||
|
||||
@DatabaseField
|
||||
var method: String = "aes-256-cfb"
|
||||
|
||||
@DatabaseField
|
||||
var route: String = "all"
|
||||
|
||||
@DatabaseField
|
||||
var remoteDns: String = "8.8.8.8"
|
||||
|
||||
@DatabaseField
|
||||
var proxyApps: Boolean = _
|
||||
|
||||
@DatabaseField
|
||||
var bypass: Boolean = _
|
||||
|
||||
@DatabaseField
|
||||
var udpdns: Boolean = _
|
||||
|
||||
@DatabaseField
|
||||
var auth: Boolean = _
|
||||
|
||||
@DatabaseField
|
||||
var ipv6: Boolean = _
|
||||
|
||||
@DatabaseField(dataType = DataType.LONG_STRING)
|
||||
var individual: String = ""
|
||||
|
||||
@DatabaseField
|
||||
var tx: Long = _
|
||||
|
||||
@DatabaseField
|
||||
var rx: Long = _
|
||||
|
||||
@DatabaseField
|
||||
val date: java.util.Date = new java.util.Date()
|
||||
|
||||
@DatabaseField
|
||||
var userOrder: Long = _
|
||||
|
||||
@DatabaseField
|
||||
var kcp: Boolean = _
|
||||
|
||||
@DatabaseField
|
||||
var kcpPort: Int = 8399
|
||||
|
||||
@DatabaseField
|
||||
var kcpcli: String = "--crypt none --mode normal --mtu 1200 --nocomp --dscp 46 --parityshard 0"
|
||||
|
||||
def formattedAddress: String = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort)
|
||||
def nameIsEmpty: Boolean = name == null || name.isEmpty
|
||||
def getName: String = if (nameIsEmpty) formattedAddress else name
|
||||
|
||||
override def toString: String = "ss://" + Base64.encodeToString("%s%s:%s@%s:%d".formatLocal(Locale.ENGLISH,
|
||||
method, if (auth) "-auth" else "", password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) +
|
||||
(if (TextUtils.isEmpty(name)) "" else '#' + URLEncoder.encode(name, "utf-8"))
|
||||
|
||||
def isMethodUnsafe: Boolean = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method)
|
||||
|
||||
def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor
|
||||
.putString(Key.name, name)
|
||||
.putString(Key.host, host)
|
||||
.putInt(Key.localPort, localPort)
|
||||
.putInt(Key.remotePort, remotePort)
|
||||
.putString(Key.password, password)
|
||||
.putString(Key.route, route)
|
||||
.putString(Key.remoteDns, remoteDns)
|
||||
.putString(Key.method, method)
|
||||
.putBoolean(Key.proxyApps, proxyApps)
|
||||
.putBoolean(Key.bypass, bypass)
|
||||
.putBoolean(Key.udpdns, udpdns)
|
||||
.putBoolean(Key.auth, auth)
|
||||
.putBoolean(Key.ipv6, ipv6)
|
||||
.putString(Key.individual, individual)
|
||||
.putBoolean(Key.kcp, kcp)
|
||||
.putInt(Key.kcpPort, kcpPort)
|
||||
.putString(Key.kcpcli, kcpcli)
|
||||
.remove(Key.dirty)
|
||||
def deserialize(pref: SharedPreferences) {
|
||||
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
|
||||
name = pref.getString(Key.name, null)
|
||||
host = pref.getString(Key.host, null)
|
||||
localPort = pref.getInt(Key.localPort, 0)
|
||||
remotePort = pref.getInt(Key.remotePort, 0)
|
||||
password = pref.getString(Key.password, null)
|
||||
method = pref.getString(Key.method, null)
|
||||
route = pref.getString(Key.route, null)
|
||||
remoteDns = pref.getString(Key.remoteDns, null)
|
||||
proxyApps = pref.getBoolean(Key.proxyApps, false)
|
||||
bypass = pref.getBoolean(Key.bypass, false)
|
||||
udpdns = pref.getBoolean(Key.udpdns, false)
|
||||
auth = pref.getBoolean(Key.auth, false)
|
||||
ipv6 = pref.getBoolean(Key.ipv6, false)
|
||||
individual = pref.getString(Key.individual, null)
|
||||
kcp = pref.getBoolean(Key.kcp, false)
|
||||
kcpPort = pref.getInt(Key.kcpPort, 0)
|
||||
kcpcli = pref.getString(Key.kcpcli, null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*******************************************************************************/
|
||||
/* */
|
||||
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
|
||||
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
|
||||
/* */
|
||||
/* This program is free software: you can redistribute it and/or modify */
|
||||
/* it under the terms of the GNU General Public License as published by */
|
||||
/* the Free Software Foundation, either version 3 of the License, or */
|
||||
/* (at your option) any later version. */
|
||||
/* */
|
||||
/* This program is distributed in the hope that it will be useful, */
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
|
||||
/* GNU General Public License for more details. */
|
||||
/* */
|
||||
/* You should have received a copy of the GNU General Public License */
|
||||
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
/* */
|
||||
/*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.database
|
||||
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.ProfilesFragment
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
|
||||
object ProfileManager {
|
||||
private final val TAG = "ProfileManager"
|
||||
}
|
||||
|
||||
class ProfileManager(dbHelper: DBHelper) {
|
||||
import ProfileManager._
|
||||
|
||||
def createProfile(p: Profile = null): Profile = {
|
||||
val profile = if (p == null) new Profile else p
|
||||
profile.id = 0
|
||||
try {
|
||||
app.currentProfile match {
|
||||
case Some(oldProfile) =>
|
||||
// Copy Feature Settings from old profile
|
||||
profile.route = oldProfile.route
|
||||
profile.ipv6 = oldProfile.ipv6
|
||||
profile.proxyApps = oldProfile.proxyApps
|
||||
profile.bypass = oldProfile.bypass
|
||||
profile.individual = oldProfile.individual
|
||||
profile.udpdns = oldProfile.udpdns
|
||||
case _ =>
|
||||
}
|
||||
val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)")
|
||||
.prepareStatementString).getFirstResult
|
||||
if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1
|
||||
dbHelper.profileDao.createOrUpdate(profile)
|
||||
if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.add(profile)
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "addProfile", ex)
|
||||
app.track(ex)
|
||||
}
|
||||
profile
|
||||
}
|
||||
|
||||
def updateProfile(profile: Profile): Boolean = {
|
||||
try {
|
||||
dbHelper.profileDao.update(profile)
|
||||
true
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "updateProfile", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def getProfile(id: Int): Option[Profile] = {
|
||||
try {
|
||||
dbHelper.profileDao.queryForId(id) match {
|
||||
case profile: Profile => Option(profile)
|
||||
case _ => None
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "getProfile", ex)
|
||||
app.track(ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def delProfile(id: Int): Boolean = {
|
||||
try {
|
||||
dbHelper.profileDao.deleteById(id)
|
||||
if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.removeId(id)
|
||||
true
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "delProfile", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def getFirstProfile: Option[Profile] = {
|
||||
try {
|
||||
val result = dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare)
|
||||
if (result.size == 1) Option(result.get(0)) else None
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
app.track(ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getAllProfiles: Option[List[Profile]] = {
|
||||
try {
|
||||
import scala.collection.JavaConversions._
|
||||
Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList)
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
app.track(ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user