Initial draft for porting this project to Gradle + Kotlin
This is one of my stupidest idea ever.
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* 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
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.LocaleList
|
||||
import android.os.Looper
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
import android.util.Log
|
||||
import com.evernote.android.job.JobManager
|
||||
import com.github.shadowsocks.acl.AclSyncJob
|
||||
import com.github.shadowsocks.bg.BaseService
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.database.ProfileManager
|
||||
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
|
||||
import com.github.shadowsocks.preference.DataStore
|
||||
import com.github.shadowsocks.preference.IconListPreference
|
||||
import com.github.shadowsocks.utils.Action
|
||||
import com.github.shadowsocks.utils.Key
|
||||
import com.github.shadowsocks.utils.TcpFastOpen
|
||||
import com.github.shadowsocks.utils.broadcastReceiver
|
||||
import com.google.android.gms.analytics.GoogleAnalytics
|
||||
import com.google.android.gms.analytics.HitBuilders
|
||||
import com.google.android.gms.analytics.StandardExceptionParser
|
||||
import com.google.android.gms.analytics.Tracker
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||
import com.j256.ormlite.logger.LocalLog
|
||||
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class App : Application() {
|
||||
companion object {
|
||||
lateinit var app: App
|
||||
private const val TAG = "ShadowsocksApplication"
|
||||
|
||||
// The ones in Locale doesn't have script included
|
||||
private val SIMPLIFIED_CHINESE by lazy {
|
||||
if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hans-CN") else Locale.SIMPLIFIED_CHINESE
|
||||
}
|
||||
private val TRADITIONAL_CHINESE by lazy {
|
||||
if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hant-TW") else Locale.TRADITIONAL_CHINESE
|
||||
}
|
||||
}
|
||||
|
||||
val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
|
||||
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) }
|
||||
val info: PackageInfo by lazy { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) }
|
||||
|
||||
fun startService() {
|
||||
val intent = Intent(this, BaseService.serviceClass.java)
|
||||
if (Build.VERSION.SDK_INT >= 26) startForegroundService(intent) else startService(intent)
|
||||
}
|
||||
fun reloadService() = sendBroadcast(Intent(Action.RELOAD))
|
||||
fun stopService() = sendBroadcast(Intent(Action.CLOSE))
|
||||
|
||||
val currentProfile: Profile? get() = ProfileManager.getProfile(DataStore.profileId)
|
||||
|
||||
fun switchProfile(id: Int): Profile {
|
||||
val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile()
|
||||
DataStore.profileId = result.id
|
||||
return result
|
||||
}
|
||||
|
||||
// send event
|
||||
fun track(category: String, action: String) = tracker.send(HitBuilders.EventBuilder()
|
||||
.setCategory(category)
|
||||
.setAction(action)
|
||||
.setLabel(BuildConfig.VERSION_NAME)
|
||||
.build())
|
||||
fun track(t: Throwable) = track(Thread.currentThread(), t)
|
||||
fun track(thread: Thread, t: Throwable) {
|
||||
tracker.send(HitBuilders.ExceptionBuilder()
|
||||
.setDescription(StandardExceptionParser(this, null).getDescription(thread.name, t))
|
||||
.setFatal(false)
|
||||
.build())
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
private fun checkChineseLocale(config: Configuration) {
|
||||
fun check(locale: Locale): Locale? {
|
||||
if (locale.language != "zh") return null
|
||||
when (locale.country) { "CN", "TW" -> return null }
|
||||
if (Build.VERSION.SDK_INT >= 21) when (locale.script) {
|
||||
"Hans" -> return SIMPLIFIED_CHINESE
|
||||
"Hant" -> return TRADITIONAL_CHINESE
|
||||
else -> Log.w(TAG, "Unknown zh locale script: ${locale.script}. Falling back to trying countries...")
|
||||
}
|
||||
when (locale.country) {
|
||||
"SG" -> return SIMPLIFIED_CHINESE
|
||||
"HK", "MO" -> return TRADITIONAL_CHINESE
|
||||
}
|
||||
Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN..."
|
||||
.format(Locale.ENGLISH, if (Build.VERSION.SDK_INT >= 21) locale.toLanguageTag() else locale))
|
||||
return SIMPLIFIED_CHINESE
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 24) @RequiresApi(24) {
|
||||
val localeList = config.locales
|
||||
var changed = false
|
||||
val newList = Array<Locale>(localeList.size(), { i ->
|
||||
val locale = localeList[i]
|
||||
val newLocale = check(locale)
|
||||
if (newLocale == null) locale else {
|
||||
changed = true
|
||||
newLocale
|
||||
}
|
||||
})
|
||||
if (changed) {
|
||||
val newConfig = Configuration(config)
|
||||
newConfig.locales = LocaleList(*(newList.distinct().toTypedArray()))
|
||||
val res = resources
|
||||
res.updateConfiguration(newConfig, res.displayMetrics)
|
||||
}
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
val newLocale = check(config.locale)
|
||||
if (newLocale != null) {
|
||||
val newConfig = Configuration(config)
|
||||
@Suppress("DEPRECATION")
|
||||
newConfig.locale = newLocale
|
||||
val res = resources
|
||||
res.updateConfiguration(newConfig, res.displayMetrics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
app = this
|
||||
if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
checkChineseLocale(resources.configuration)
|
||||
|
||||
FirebaseApp.initializeApp(this)
|
||||
remoteConfig.setDefaults(R.xml.default_configs)
|
||||
remoteConfig.fetch().addOnCompleteListener {
|
||||
if (it.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")
|
||||
}
|
||||
|
||||
JobManager.create(this).addJobCreator(AclSyncJob)
|
||||
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
|
||||
BottomSheetPreferenceDialogFragment::class.java)
|
||||
|
||||
TcpFastOpen.enabled(DataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
|
||||
|
||||
if (DataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
|
||||
val assetManager = assets
|
||||
for (dir in arrayOf("acl", "overture"))
|
||||
try {
|
||||
for (file in assetManager.list(dir)) assetManager.open(dir + '/' + file).use { input ->
|
||||
File(filesDir, file).outputStream().use { output -> input.copyTo(output) }
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, e.message)
|
||||
app.track(e)
|
||||
}
|
||||
DataStore.putLong(Key.assetUpdateTime, info.lastUpdateTime)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
|
||||
val nm = getSystemService(NotificationManager::class.java)
|
||||
nm.createNotificationChannels(listOf(
|
||||
NotificationChannel("service-vpn", getText(R.string.service_vpn),
|
||||
NotificationManager.IMPORTANCE_LOW),
|
||||
NotificationChannel("service-proxy", getText(R.string.service_proxy),
|
||||
NotificationManager.IMPORTANCE_LOW),
|
||||
NotificationChannel("service-transproxy", getText(R.string.service_transproxy),
|
||||
NotificationManager.IMPORTANCE_LOW)))
|
||||
nm.deleteNotificationChannel("service-nat") // NAT mode is gone for good
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
checkChineseLocale(newConfig)
|
||||
}
|
||||
|
||||
fun listenForPackageChanges(callback: () -> Unit): BroadcastReceiver {
|
||||
val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED)
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
filter.addDataScheme("package")
|
||||
val result = broadcastReceiver { _, intent ->
|
||||
if (intent.action != Intent.ACTION_PACKAGE_REMOVED ||
|
||||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) callback()
|
||||
}
|
||||
app.registerReceiver(result, filter)
|
||||
return result
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user