add black/white list

This commit is contained in:
Kr328 committed 2019-12-02 21:43:19 +08:00
1 parent e7d59ec633
commit 961f9c7633
15 files changed
+464 -26

No files matched your search

@@ -1,10 +1,66 @@
package com.github.kr328.clash
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.View
import android.view.*
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.kr328.clash.service.ClashSettingService
import kotlinx.android.synthetic.main.activity_setting_access.*
import kotlin.concurrent.thread
class SettingAccessActivity : BaseActivity() {
private data class AppInfo(val packageName: String, val name: String, val icon: Drawable)
private class AppListAdapter(val context: Context):
RecyclerView.Adapter<AppListAdapter.Holder>() {
var applications: List<AppInfo> = emptyList()
var selected: MutableSet<String> = mutableSetOf()
class Holder(val view: View) : RecyclerView.ViewHolder(view) {
val name: TextView = view.findViewById(R.id.adapter_access_app_name)
val packageName: TextView = view.findViewById(R.id.adapter_access_app_package_name)
val icon: ImageView = view.findViewById(R.id.adapter_access_app_icon)
val checkbox: CheckBox = view.findViewById(R.id.adapter_access_app_checkbox)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
return Holder(LayoutInflater.from(context)
.inflate(R.layout.adapter_access_app, parent, false))
}
override fun getItemCount(): Int {
return applications.size
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val current = applications[position]
holder.name.text = current.name
holder.packageName.text = current.packageName
holder.icon.setImageDrawable(current.icon)
holder.checkbox.isChecked = current.packageName in selected
holder.view.setOnClickListener {
if ( holder.checkbox.isChecked )
selected.remove(current.packageName)
else
selected.add(current.packageName)
notifyItemChanged(position)
}
}
}
private var showList: Boolean = false
private var listLoaded: Boolean = false
private var hidden: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_setting_access)
@@ -16,8 +72,9 @@ class SettingAccessActivity : BaseActivity() {
activity_setting_access_allow.isChecked = false
activity_setting_access_disallow.isChecked = false
activity_setting_access_divider.visibility = View.GONE
activity_setting_access_app_list.visibility = View.GONE
showList = false
updateListStatus()
}
activity_setting_access_allow.setOnClickListener {
@@ -25,8 +82,9 @@ class SettingAccessActivity : BaseActivity() {
activity_setting_access_allow.isChecked = true
activity_setting_access_disallow.isChecked = false
activity_setting_access_divider.visibility = View.VISIBLE
activity_setting_access_app_list.visibility = View.VISIBLE
showList = true
updateListStatus()
}
activity_setting_access_disallow.setOnClickListener {
@@ -34,8 +92,175 @@ class SettingAccessActivity : BaseActivity() {
activity_setting_access_allow.isChecked = false
activity_setting_access_disallow.isChecked = true
activity_setting_access_divider.visibility = View.VISIBLE
activity_setting_access_app_list.visibility = View.VISIBLE
showList = true
updateListStatus()
}
activity_setting_access_app_list.apply {
adapter = AppListAdapter(this@SettingAccessActivity)
layoutManager = LinearLayoutManager(this@SettingAccessActivity)
isNestedScrollingEnabled = false
}
runClash {
val settings = it.settingService
runOnUiThread {
when ( settings.accessControlMode ) {
ClashSettingService.ACCESS_CONTROL_MODE_ALLOW_ALL ->
activity_setting_access_allow_all.performClick()
ClashSettingService.ACCESS_CONTROL_MODE_ALLOW ->
activity_setting_access_allow.performClick()
ClashSettingService.ACCESS_CONTROL_MODE_DISALLOW ->
activity_setting_access_disallow.performClick()
}
}
val exclude = resources.getStringArray(R.array.default_disallow_application).toSet()
val applications = packageManager.getInstalledApplications(0)
.filterNot {
exclude.contains(it.packageName)
}
.map { app ->
AppInfo(app.packageName,
app.loadLabel(packageManager).toString(),
app.loadIcon(packageManager))
}
.sortedBy { app ->
app.name
}
val selected = settings.accessControlApps.toMutableSet()
runOnUiThread {
activity_setting_access_app_list.apply {
(adapter as AppListAdapter).apply {
this.applications = applications
this.selected = selected
notifyDataSetChanged()
}
}
listLoaded = true
updateListStatus()
}
}
}
override fun onStop() {
super.onStop()
runClash {
val mode = when {
activity_setting_access_allow_all.isChecked ->
ClashSettingService.ACCESS_CONTROL_MODE_ALLOW_ALL
activity_setting_access_allow.isChecked ->
ClashSettingService.ACCESS_CONTROL_MODE_ALLOW
activity_setting_access_disallow.isChecked ->
ClashSettingService.ACCESS_CONTROL_MODE_DISALLOW
else -> return@runClash
}
if ( listLoaded )
it.settingService.setAccessControl(mode,
(activity_setting_access_app_list.adapter as AppListAdapter)
.selected.toTypedArray())
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_setting_access, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when ( item.itemId ) {
R.id.menu_setting_access_hide -> {
if ( hidden ) {
activity_setting_access_allow_all.visibility = View.VISIBLE
activity_setting_access_allow.visibility = View.VISIBLE
activity_setting_access_disallow.visibility = View.VISIBLE
item.title = getString(R.string.access_setting_hide_header)
hidden = false
}
else {
if ( !activity_setting_access_allow_all.isChecked )
activity_setting_access_allow_all.visibility = View.GONE
if ( !activity_setting_access_allow.isChecked )
activity_setting_access_allow.visibility = View.GONE
if ( !activity_setting_access_disallow.isChecked )
activity_setting_access_disallow.visibility = View.GONE
item.title = getString(R.string.access_setting_show_header)
hidden = true
}
}
R.id.menu_setting_access_select_all -> {
thread {
val selected = (activity_setting_access_app_list.adapter as AppListAdapter)
.applications.map { it.packageName }.toMutableSet()
runOnUiThread {
(activity_setting_access_app_list.adapter as AppListAdapter).apply {
this.selected = selected
notifyItemRangeChanged(0, applications.size)
}
}
}
}
R.id.menu_setting_access_select_invert -> {
thread {
(activity_setting_access_app_list.adapter as AppListAdapter).apply {
selected = (applications.map { it.packageName }.toSet() - selected).toMutableSet()
}
runOnUiThread {
(activity_setting_access_app_list.adapter as AppListAdapter).apply {
notifyItemRangeChanged(0, applications.size)
}
}
}
}
R.id.menu_setting_access_clean_selection -> {
(activity_setting_access_app_list.adapter as AppListAdapter).apply {
selected = mutableSetOf()
notifyItemRangeChanged(0, applications.size)
}
}
else -> return super.onOptionsItemSelected(item)
}
return true
}
private fun updateListStatus() {
if ( showList )
activity_setting_access_divider.visibility = View.VISIBLE
else
activity_setting_access_divider.visibility = View.GONE
if ( showList ) {
if ( listLoaded )
activity_setting_access_loading.visibility = View.GONE
else
activity_setting_access_loading.visibility = View.VISIBLE
}
else
activity_setting_access_loading.visibility = View.GONE
if ( showList && listLoaded )
activity_setting_access_app_list.visibility = View.VISIBLE
else
activity_setting_access_app_list.visibility = View.GONE
}
}
@@ -69,6 +69,12 @@ class RadioFatItem @JvmOverloads constructor(
operationClickable.setOnLongClickListener(l)
}
override fun performClick(): Boolean {
super.performClick()
return clickable.performClick()
}
init {
with(LayoutInflater.from(context).inflate(R.layout.view_radio_fat_item, this, true)) {
titleView = findViewById(R.id.view_radio_fat_item_title)
@@ -16,6 +16,8 @@
android:layout_height="60dp" />
</com.google.android.material.appbar.AppBarLayout>
<com.github.kr328.clash.view.RadioFatItem
app:title="@string/access_setting_allow_all"
app:summary="@string/access_setting_allow_all_summary"
@@ -45,8 +47,18 @@
android:layout_height="1dp"
android:background="@color/lightGray"/>
<ListView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/activity_setting_access_app_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/activity_setting_access_loading"
android:text="@string/access_setting_loading"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginStart="70dp"
android:visibility="gone"/>
</LinearLayout>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_marginTop="5dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="70dp"
android:focusable="true"
android:clickable="true"
android:background="?attr/selectableItemBackground">
<ImageView
android:id="@+id/adapter_access_app_icon"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="20dp"
android:layout_width="30dp"
android:layout_height="30dp"
android:contentDescription="@string/app_name" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/adapter_access_app_icon"
android:layout_toStartOf="@id/adapter_access_app_checkbox"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/adapter_access_app_name"
android:textColor="@android:color/black"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/adapter_access_app_package_name"
android:layout_marginTop="2dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/adapter_access_app_checkbox"
android:focusable="false"
android:clickable="false"
android:layout_alignParentEnd="true"
android:gravity="center"
android:layout_width="50dp"
android:layout_height="70dp"/>
</RelativeLayout>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_setting_access_hide"
android:title="@string/access_setting_hide_header" />
<item
android:id="@+id/menu_setting_access_select_all"
android:title="@string/access_setting_select_all" />
<item
android:id="@+id/menu_setting_access_select_invert"
android:title="@string/access_setting_select_invert" />
<item
android:id="@+id/menu_setting_access_clean_selection"
android:title="@string/access_setting_clean_selection" />
</menu>
+6
View File
@@ -64,4 +64,10 @@
<string name="access_setting_allow_summary">Allow follow apps access vpn</string>
<string name="access_setting_disallow">Disallow</string>
<string name="access_setting_disallow_summary">Disallow follow apps access vpn</string>
<string name="access_setting_loading">Loading</string>
<string name="access_setting_show_header">Show Header</string>
<string name="access_setting_hide_header">Hide Header</string>
<string name="access_setting_select_all">Select All</string>
<string name="access_setting_select_invert">Select Invert</string>
<string name="access_setting_clean_selection">Clear Selection</string>
</resources>
+4 -1
View File
@@ -26,8 +26,11 @@ android {
}
}
compileOptions {
targetCompatibility = 1.8
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
@@ -3,6 +3,7 @@ package com.github.kr328.clash.service;
import com.github.kr328.clash.service.IClashEventObserver;
import com.github.kr328.clash.service.IClashEventService;
import com.github.kr328.clash.service.IClashProfileService;
import com.github.kr328.clash.service.IClashSettingService;
import com.github.kr328.clash.core.event.Event;
import com.github.kr328.clash.core.model.ProxyPacket;
@@ -10,6 +11,7 @@ interface IClashService {
// Services
IClashEventService getEventService();
IClashProfileService getProfileService();
IClashSettingService getSettingService();
// Status
ProcessEvent getCurrentProcessStatus();
@@ -0,0 +1,14 @@
package com.github.kr328.clash.service;
interface IClashSettingService {
// Set
void setIPv6Enabled(boolean enabled);
void setBypassPrivateNetwork(boolean enabled);
void setAccessControl(int mode, in String[] applications);
// Get
boolean isIPv6Enabled();
boolean isBypassPrivateNetwork();
String[] getAccessControlApps();
int getAccessControlMode();
}
@@ -13,7 +13,6 @@ import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.event.*
import com.github.kr328.clash.core.model.ProxyPacket
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.service.data.ClashDatabase
import java.io.File
import java.io.IOException
import java.util.concurrent.Executors
@@ -24,6 +23,7 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
private val eventService = ClashEventService(this)
private val profileService = ClashProfileService(this, this)
private val settingService = ClashSettingService(this)
private lateinit var clash: Clash
private lateinit var puller: ClashEventPuller
@@ -103,6 +103,10 @@ class ClashService : Service(), IClashEventObserver, ClashEventService.Master,
return this@ClashService.profileService
}
override fun getSettingService(): IClashSettingService {
return this@ClashService.settingService
}
override fun getCurrentProcessStatus(): ProcessEvent {
return clash.process.getProcessStatus()
}
@@ -0,0 +1,58 @@
package com.github.kr328.clash.service
import android.content.Context
import androidx.core.content.edit
class ClashSettingService(context: Context): IClashSettingService.Stub() {
companion object {
const val KEY_ACCESS_CONTROL_MODE = "key_access_control_mode"
const val KEY_ACCESS_CONTROL_APPS = "ley_access_control_apps"
const val KEY_IPV6_ENABLED = "key_ipv6_enabled"
const val KEY_BYPASS_PRIVATE_NETWORK = "key_bypass_private_network"
const val ACCESS_CONTROL_MODE_ALLOW_ALL = 0
const val ACCESS_CONTROL_MODE_ALLOW = 1
const val ACCESS_CONTROL_MODE_DISALLOW = 2
}
private val preference by lazy {
context.getSharedPreferences("clash_service", Context.MODE_PRIVATE)
}
override fun setAccessControl(mode: Int, applications: Array<out String>?) {
require(mode in 0..3)
preference.edit {
putInt(KEY_ACCESS_CONTROL_MODE, mode)
putStringSet(KEY_ACCESS_CONTROL_APPS, applications?.toSet() ?: emptySet())
}
}
override fun setIPv6Enabled(enabled: Boolean) {
preference.edit {
putBoolean(KEY_IPV6_ENABLED, enabled)
}
}
override fun setBypassPrivateNetwork(enabled: Boolean) {
preference.edit {
putBoolean(KEY_BYPASS_PRIVATE_NETWORK, enabled)
}
}
override fun isBypassPrivateNetwork(): Boolean {
return preference.getBoolean(KEY_BYPASS_PRIVATE_NETWORK, true)
}
override fun isIPv6Enabled(): Boolean {
return preference.getBoolean(KEY_IPV6_ENABLED, true)
}
override fun getAccessControlApps(): Array<String> {
return preference.getStringSet(KEY_ACCESS_CONTROL_APPS, emptySet())?.toTypedArray()!!
}
override fun getAccessControlMode(): Int {
return preference.getInt(KEY_ACCESS_CONTROL_MODE, ACCESS_CONTROL_MODE_ALLOW_ALL)
}
}
@@ -8,6 +8,7 @@ import android.content.ServiceConnection
import android.net.VpnService
import android.os.*
import com.github.kr328.clash.core.event.*
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.service.net.DefaultNetworkObserver
class TunService : VpnService(), IClashEventObserver {
@@ -23,6 +24,7 @@ class TunService : VpnService(), IClashEventObserver {
private lateinit var fileDescriptor: ParcelFileDescriptor
private lateinit var clash: IClashService
private lateinit var defaultNetworkObserver: DefaultNetworkObserver
private lateinit var settings: ClashSettingService
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
stopSelf()
@@ -53,9 +55,10 @@ class TunService : VpnService(), IClashEventObserver {
return
}
settings = ClashSettingService(this)
fileDescriptor = Builder()
.addAddress(PRIVATE_VLAN4_CLIENT, 30)
.addAddress(PRIVATE_VLAN6_CLIENT, 126)
.addAddress()
.addDnsServer(PRIVATE_DEFAULT_DNS)
.addBypassApplications()
.addBypassPrivateRoute()
@@ -120,22 +123,65 @@ class TunService : VpnService(), IClashEventObserver {
private fun Builder.addBypassPrivateRoute(): Builder {
// IPv4
resources.getStringArray(R.array.bypass_private_route).forEach {
val address = it.split("/")
addRoute(address[0], address[1].toInt())
if ( settings.isBypassPrivateNetwork ) {
resources.getStringArray(R.array.bypass_private_route).forEach {
val address = it.split("/")
addRoute(address[0], address[1].toInt())
}
}
else {
addRoute("0.0.0.0", 0)
}
// IPv6
addRoute("::", 0)
if ( settings.isIPv6Enabled ) {
addRoute("::", 0)
}
return this
}
private fun Builder.addBypassApplications(): Builder {
resources.getStringArray(R.array.default_bypass_application)
.forEach {
runCatching { this.addDisallowedApplication(it) }
when ( settings.accessControlMode ) {
ClashSettingService.ACCESS_CONTROL_MODE_ALLOW_ALL -> {
for ( app in resources.getStringArray(R.array.default_disallow_application) ) {
runCatching {
addDisallowedApplication(app)
}
}
}
ClashSettingService.ACCESS_CONTROL_MODE_ALLOW -> {
for ( app in (settings.accessControlApps.toSet() -
resources.getStringArray(R.array.default_disallow_application)) ) {
runCatching {
addAllowedApplication(app)
}.onFailure {
Log.w("Package $app not found")
}
}
}
ClashSettingService.ACCESS_CONTROL_MODE_DISALLOW -> {
for ( app in (settings.accessControlApps.toSet() +
resources.getStringArray(R.array.default_disallow_application)) ) {
runCatching {
addDisallowedApplication(app)
}.onFailure {
Log.w("Package $app not found")
}
}
}
}
return this
}
private fun Builder.addAddress(): Builder {
addAddress(PRIVATE_VLAN4_CLIENT, 30)
if ( settings.isIPv6Enabled )
addAddress(PRIVATE_VLAN6_CLIENT, 126)
return this
}
@@ -1,11 +1,9 @@
package com.github.kr328.clash.service.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import androidx.room.*
@Entity(tableName = "profile_select_proxies",
indices = [Index("profile_id")],
foreignKeys = [ForeignKey(entity = ClashProfileEntity::class,
childColumns = ["profile_id"],
parentColumns = ["id"],
+1 -1
View File
@@ -40,7 +40,7 @@
<item>224.0.0.0/3</item>
</string-array>
<string-array name="default_bypass_application">
<string-array name="default_disallow_application">
<item>com.github.kr328.clash</item>
<item>com.android.networkstack</item>
</string-array>