Initial draft for porting this project to Gradle + Kotlin

This is one of my stupidest idea ever.
This commit is contained in:
Mygod
2017-12-03 10:20:57 -08:00
parent 2f682ef3ec
commit 3e8b254118
206 changed files with 7885 additions and 7475 deletions
@@ -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
}
}