add database
This commit is contained in:
12 files changed
+134
-24
No files matched your search
+7
-1
@@ -1,13 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
defaultConfig {
|
||||
applicationId "com.github.kr328.clash"
|
||||
minSdkVersion 23
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
@@ -21,10 +22,15 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation project(":core")
|
||||
implementation project(":service")
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.core:core-ktx:1.1.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
}
|
||||
@@ -4,11 +4,6 @@ import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_CODE = 233
|
||||
private const val VPN_REQUEST = 234
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
@@ -6,13 +6,5 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Open" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
+1
-1
@@ -7,7 +7,7 @@ android {
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 23
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
@@ -40,7 +40,10 @@ class Clash(
|
||||
|
||||
@Throws(IOException::class)
|
||||
@Synchronized
|
||||
fun loadProfile(url: Uri) {
|
||||
fun loadProfile(url: Uri?) {
|
||||
if ( url == null || url.scheme != "content" )
|
||||
return
|
||||
|
||||
synchronized(cacheFile) {
|
||||
context.contentResolver.openInputStream(url)?.use {
|
||||
it.copyTo(FileOutputStream(cacheFile))
|
||||
|
||||
@@ -9,7 +9,7 @@ android {
|
||||
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 23
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
@@ -24,13 +24,18 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility = 1.8
|
||||
sourceCompatibility = 1.8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation project(":core")
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.1.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "androidx.core:core-ktx:1.1.0"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.github.kr328.clash.service;
|
||||
|
||||
import com.github.kr328.clash.service.IClashObserver;
|
||||
import com.github.kr328.clash.core.ClashProcessStatus;
|
||||
|
||||
interface IClashService {
|
||||
void start();
|
||||
void stop();
|
||||
ClashProcessStatus getClashProcessStatus();
|
||||
void registerObserver(String id, boolean notifyCurrent, in IClashObserver observer);
|
||||
void unregisterObserver(String id);
|
||||
void startTunDevice(in ParcelFileDescriptor fd, int mtu);
|
||||
|
||||
@@ -2,25 +2,32 @@ package com.github.kr328.clash.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.ClashProcessStatus
|
||||
import com.github.kr328.clash.service.data.ClashDatabase
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class ClashService : Service() {
|
||||
companion object {
|
||||
private val TAG = "ClashForAndroid"
|
||||
}
|
||||
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private lateinit var clash: Clash
|
||||
private var lastProfile: String? = null
|
||||
|
||||
private var status = ClashProcessStatus(ClashProcessStatus.STATUS_STOPPED)
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
for ( observer in observers.values ) {
|
||||
for (observer in observers.values) {
|
||||
runCatching {
|
||||
observer.onStatusChanged(status)
|
||||
}
|
||||
@@ -28,14 +35,45 @@ class ClashService : Service() {
|
||||
}
|
||||
private val observers = mutableMapOf<String, IClashObserver>()
|
||||
|
||||
private val defaultProfileObserver = object: Observer<String?> {
|
||||
override fun onChanged(url: String?) {
|
||||
executor.submit {
|
||||
if (url == null)
|
||||
clash.stop()
|
||||
if (url == lastProfile)
|
||||
return@submit
|
||||
try {
|
||||
clash.loadProfile(Uri.parse(url))
|
||||
lastProfile = url
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Load profile failure")
|
||||
|
||||
if (lastProfile == null) {
|
||||
clash.stop()
|
||||
} else {
|
||||
val p = lastProfile
|
||||
lastProfile = null
|
||||
ClashDatabase.getInstance(this@ClashService)
|
||||
.openClashProfileDao()
|
||||
.setDefaultProfile(p ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ClashServiceImpl : IClashService.Stub() {
|
||||
override fun registerObserver(id: String?, notifyCurrent: Boolean, observer: IClashObserver?) {
|
||||
override fun registerObserver(
|
||||
id: String?,
|
||||
notifyCurrent: Boolean,
|
||||
observer: IClashObserver?
|
||||
) {
|
||||
if (id == null || observer == null)
|
||||
throw RemoteException()
|
||||
|
||||
observers[id] = observer
|
||||
|
||||
if ( notifyCurrent )
|
||||
if (notifyCurrent)
|
||||
observer.onStatusChanged(status)
|
||||
|
||||
|
||||
@@ -67,6 +105,10 @@ class ClashService : Service() {
|
||||
clash.stop()
|
||||
}
|
||||
|
||||
override fun getClashProcessStatus(): ClashProcessStatus {
|
||||
return status
|
||||
}
|
||||
|
||||
override fun startTunDevice(fd: ParcelFileDescriptor, mtu: Int) {
|
||||
try {
|
||||
clash.startTunDevice(fd.fileDescriptor, mtu)
|
||||
@@ -85,6 +127,14 @@ class ClashService : Service() {
|
||||
cacheDir.resolve("clash_controller")
|
||||
) {
|
||||
this.status = it
|
||||
|
||||
when (it.status) {
|
||||
ClashProcessStatus.STATUS_STARTED ->
|
||||
ClashDatabase.getInstance(this)
|
||||
.openClashProfileDao()
|
||||
.observeDefaultProfileUrl()
|
||||
.observeForever(defaultProfileObserver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +144,12 @@ class ClashService : Service() {
|
||||
|
||||
override fun onDestroy() {
|
||||
clash.stop()
|
||||
executor.shutdown()
|
||||
|
||||
ClashDatabase.getInstance(this)
|
||||
.openClashProfileDao()
|
||||
.observeDefaultProfileUrl()
|
||||
.removeObserver(defaultProfileObserver)
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class TunService : VpnService() {
|
||||
service
|
||||
) ?: throw NullPointerException()
|
||||
|
||||
clash.start()
|
||||
|
||||
clash.registerObserver("tun", false, object: IClashObserver.Stub() {
|
||||
override fun onStatusChanged(status: ClashProcessStatus?) {
|
||||
if ( status == null )
|
||||
@@ -45,6 +45,7 @@ class TunService : VpnService() {
|
||||
}
|
||||
}
|
||||
})
|
||||
clash.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.kr328.clash.service.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import java.lang.NullPointerException
|
||||
|
||||
@Database(version = 1, exportSchema = false, entities = [ClashProfileEntity::class])
|
||||
abstract class ClashDatabase : RoomDatabase() {
|
||||
abstract fun openClashProfileDao(): ClashProfileDao
|
||||
|
||||
companion object {
|
||||
private var instance: ClashDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): ClashDatabase {
|
||||
if ( instance == null )
|
||||
instance = Room.databaseBuilder(context.applicationContext,
|
||||
ClashDatabase::class.java,
|
||||
"clash-config")
|
||||
.enableMultiInstanceInvalidation()
|
||||
.build()
|
||||
return instance ?: throw NullPointerException()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.github.kr328.clash.service.data
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface ClashProfileDao {
|
||||
@Query("UPDATE profiles SET `default` = CASE WHEN url = :url THEN 1 ELSE 0 END")
|
||||
fun setDefaultProfile(url: String)
|
||||
|
||||
@Query("SELECT url FROM profiles WHERE `default` = 1 LIMIT 1")
|
||||
fun observeDefaultProfileUrl(): LiveData<String?>
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.github.kr328.clash.service.data
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(tableName = "profiles", primaryKeys = ["url"])
|
||||
data class ClashProfileEntity(@ColumnInfo(name = "url") val url: String,
|
||||
@ColumnInfo(name = "default") val defaultProfile: Boolean,
|
||||
@ColumnInfo(name = "writable") val writable: Boolean,
|
||||
@ColumnInfo(name = "last_update") val lastUpdate: Long)
|
||||
Reference in new issue
Block a user