Skip to content

Commit

Permalink
Initial design for Android TV
Browse files Browse the repository at this point in the history
  • Loading branch information
Mygod committed Aug 23, 2018
1 parent 9595963 commit f09f12c
Show file tree
Hide file tree
Showing 117 changed files with 1,672 additions and 677 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,5 @@ core/src/overture/src/github.com/Sirupsen
core/src/overture/src/github.com/miekg
core/src/overture/src/golang.org

# work in progress
tv/

# release apks
*.apk
24 changes: 21 additions & 3 deletions core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
buildToolsVersion rootProject.buildToolsVersion
Expand All @@ -20,6 +22,11 @@ android {
arguments "-j${Runtime.runtime.availableProcessors()}"
}
}
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}

buildTypes {
Expand Down Expand Up @@ -56,15 +63,26 @@ task goClean(type: Exec) {
}

tasks.whenTaskAdded { task ->
if ((task.name == 'generateJsonModelDebug' ||
task.name == 'generateJsonModelRelease')) {
task.dependsOn(goBuild)
if ((task.name == 'generateJsonModelDebug' || task.name == 'generateJsonModelRelease')) {
task.dependsOn(goBuild)
}
}

dependencies {
api project(':plugin')
api "android.arch.work:work-runtime-ktx:$workVersion"
api "androidx.preference:preference:$androidxVersion"
api "androidx.room:room-runtime:$roomVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.9.4'
api 'com.google.firebase:firebase-config:16.0.0'
api 'com.google.firebase:firebase-core:16.0.0'
api 'com.squareup.okhttp3:okhttp:3.11.0'
api "com.takisoft.preferencex:preferencex:$preferencexVersion"
api 'eu.chainfire:libsuperuser:1.0.0.201704021214'
kapt "androidx.room:room-compiler:$roomVersion"
testImplementation "androidx.room:room-testing:$roomVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "android.arch.work:work-testing:$workVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidEspressoVersion"
}
File renamed without changes
105 changes: 104 additions & 1 deletion core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,107 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.shadowsocks.core">
xmlns:tools="http://schemas.android.com/tools"
package="com.github.shadowsocks.core"
android:installLocation="internalOnly">

<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

<uses-feature android:name="android.software.leanback"
android:required="false"/>

<application
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:backupAgent="com.github.shadowsocks.ConfigBackupHelper"
android:fullBackupContent="@xml/backup_descriptor"
android:fullBackupOnly="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:banner="@mipmap/banner">

<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true" />
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/>
<meta-data android:name="firebase_crashlytics_collection_enabled"
android:value="false"/>

<service
android:name="com.github.shadowsocks.bg.VpnService"
android:process=":bg"
android:directBootAware="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>

<service
android:name="com.github.shadowsocks.bg.TransproxyService"
android:process=":bg"
android:directBootAware="true"
android:exported="false">
</service>

<service
android:name="com.github.shadowsocks.bg.ProxyService"
android:process=":bg"
android:directBootAware="true"
android:exported="false">
</service>

<activity
android:name="com.github.shadowsocks.VpnRequestActivity"
android:theme="@style/Theme.AppCompat.Translucent"
android:excludeFromRecents="true"
android:taskAffinity=""
android:launchMode="singleTask"/>

<receiver android:name="com.github.shadowsocks.BootReceiver"
android:process=":bg"
android:directBootAware="true"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>

<!-- https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/work/workmanager/src/main/AndroidManifest.xml -->
<provider android:name="androidx.work.impl.WorkManagerInitializer"
tools:node="remove"/>
<service android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
android:process=":bg"
android:directBootAware="true"/>
<service android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
android:process=":bg"
android:directBootAware="true"/>
</application>
</manifest>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.preference.DataStore

class BootReceiver : BroadcastReceiver() {
Expand All @@ -45,6 +45,6 @@ class BootReceiver : BroadcastReceiver() {
Intent.ACTION_LOCKED_BOOT_COMPLETED -> true // constant will be folded so no need to do version checks
else -> return
}
if (DataStore.directBootAware == locked) app.startService()
if (DataStore.directBootAware == locked) Core.startService()
}
}
165 changes: 165 additions & 0 deletions core/src/main/java/com/github/shadowsocks/Core.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*******************************************************************************
* *
* Copyright (C) 2018 by Max Lv <[email protected]> *
* Copyright (C) 2018 by Mygod Studio <[email protected]> *
* *
* 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.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.UserManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.work.WorkManager
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.core.R
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.*
import com.google.firebase.FirebaseApp
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import io.fabric.sdk.android.Fabric
import java.io.File
import java.io.IOException
import kotlin.reflect.KClass

object Core {
const val TAG = "Core"

lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent
val handler by lazy { Handler(Looper.getMainLooper()) }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus ==
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
}

val currentProfile: Profile? get() =
if (DataStore.directBootAware) DirectBoot.getDeviceProfile() else ProfileManager.getProfile(DataStore.profileId)

fun switchProfile(id: Long): Profile {
val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile()
DataStore.profileId = result.id
return result
}

fun init(app: Application, configureClass: KClass<out Any>) {
this.app = app
this.configureIntent = {
PendingIntent.getActivity(it, 0, Intent(it, configureClass.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)
}

if (Build.VERSION.SDK_INT >= 24) { // migrate old files
deviceStorage.moveDatabaseFrom(app, Key.DB_PUBLIC)
val old = Acl.getFile(Acl.CUSTOM_RULES, app)
if (old.canRead()) {
Acl.getFile(Acl.CUSTOM_RULES).writeText(old.readText())
old.delete()
}
}

Fabric.with(deviceStorage, Crashlytics()) // multiple processes needs manual set-up
FirebaseApp.initializeApp(deviceStorage)
remoteConfig.setDefaults(R.xml.default_configs)
remoteConfig.fetch().addOnCompleteListener {
if (it.isSuccessful) remoteConfig.activateFetched() else {
Log.e(TAG, "Failed to fetch config")
Crashlytics.logException(it.exception)
}
}
WorkManager.initialize(deviceStorage, androidx.work.Configuration.Builder().build())

// handle data restored/crash
if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware &&
app.getSystemService<UserManager>()?.isUserUnlocked == true) DirectBoot.flushTrafficStats()
if (DataStore.tcpFastOpen) TcpFastOpen.enabledAsync(true)
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != packageInfo.lastUpdateTime) {
val assetManager = app.assets
for (dir in arrayOf("acl", "overture"))
try {
for (file in assetManager.list(dir)!!) assetManager.open("$dir/$file").use { input ->
File(deviceStorage.filesDir, file).outputStream().use { output -> input.copyTo(output) }
}
} catch (e: IOException) {
printLog(e)
}
DataStore.publicStore.putLong(Key.assetUpdateTime, packageInfo.lastUpdateTime)
}
updateNotificationChannels()
}

fun updateNotificationChannels() {
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
val nm = app.getSystemService<NotificationManager>()!!
nm.createNotificationChannels(listOf(
NotificationChannel("service-vpn", app.getText(R.string.service_vpn),
NotificationManager.IMPORTANCE_LOW),
NotificationChannel("service-proxy", app.getText(R.string.service_proxy),
NotificationManager.IMPORTANCE_LOW),
NotificationChannel("service-transproxy", app.getText(R.string.service_transproxy),
NotificationManager.IMPORTANCE_LOW)))
nm.deleteNotificationChannel("service-nat") // NAT mode is gone for good
}
}

fun getPackageInfo(packageName: String) = app.packageManager.getPackageInfo(packageName,
if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES)!!

fun startService() = ContextCompat.startForegroundService(app, Intent(app, BaseService.serviceClass.java))
fun reloadService() = app.sendBroadcast(Intent(Action.RELOAD))
fun stopService() = app.sendBroadcast(Intent(Action.CLOSE))

fun listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit): BroadcastReceiver {
val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
filter.addDataScheme("package")
val result = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) return
callback()
if (onetime) app.unregisterReceiver(this)
}
}
app.registerReceiver(result, filter)
return result
}
}
Loading

0 comments on commit f09f12c

Please sign in to comment.