add package list
This commit is contained in:
11 files changed
+297
-9
No files matched your search
+1
-1
@@ -11,7 +11,7 @@ android {
|
||||
applicationId "com.github.kr328.clash"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 29
|
||||
versionCode 10035
|
||||
versionCode 10100
|
||||
versionName "1.1.0"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
android:label="@string/interface_"
|
||||
android:exported="false"
|
||||
android:configChanges="uiMode" />
|
||||
<activity
|
||||
android:name=".AccessControlPackagesActivity"
|
||||
android:label="@string/access_control_packages"
|
||||
android:exported="false"
|
||||
android:configChanges="uiMode" />
|
||||
<service
|
||||
android:name=".LogcatService"
|
||||
android:exported="false"
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.kr328.clash.adapter.PackagesAdapter
|
||||
import kotlinx.android.synthetic.main.activity_access_control_packages.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlin.streams.toList
|
||||
|
||||
class AccessControlPackagesActivity: BaseActivity() {
|
||||
override val activityLabel: CharSequence?
|
||||
get() = getText(R.string.access_control_packages)
|
||||
private val activity: AccessControlPackagesActivity
|
||||
get() = this
|
||||
private val refreshChannel = Channel<Unit>(Channel.CONFLATED)
|
||||
|
||||
private var keyword: String = ""
|
||||
private var sort: PackagesAdapter.Sort = PackagesAdapter.Sort.NAME
|
||||
private var decrease: Boolean = false
|
||||
private var systemApp: Boolean = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_access_control_packages)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
launch {
|
||||
val apps = withContext(Dispatchers.IO) {
|
||||
val pm = packageManager
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
|
||||
packages.parallelStream()
|
||||
.map {
|
||||
PackagesAdapter.AppInfo(
|
||||
it.packageName,
|
||||
it.applicationInfo.loadLabel(pm).toString(),
|
||||
it.applicationInfo.loadIcon(pm),
|
||||
it.firstInstallTime, it.lastUpdateTime,
|
||||
it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0)
|
||||
}
|
||||
.sorted(compareBy(PackagesAdapter.AppInfo::label))
|
||||
.toList()
|
||||
}
|
||||
|
||||
val adapter = PackagesAdapter(activity, apps)
|
||||
|
||||
mainList.adapter = adapter
|
||||
mainList.layoutManager = LinearLayoutManager(activity)
|
||||
|
||||
progress.visibility = View.GONE
|
||||
|
||||
refreshChannel.send(Unit)
|
||||
|
||||
while ( isActive ) {
|
||||
refreshChannel.receive()
|
||||
|
||||
adapter.applyFilter(keyword, sort, decrease, systemApp)
|
||||
|
||||
delay(200)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.github.kr328.clash.adapter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.R
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.streams.toList
|
||||
|
||||
class PackagesAdapter(private val context: Context,
|
||||
private val apps: List<AppInfo>) :
|
||||
RecyclerView.Adapter<PackagesAdapter.Holder>() {
|
||||
data class AppInfo(val packageName: String, val label: String, val icon: Drawable,
|
||||
val installTime: Long, val updateTime: Long, val isSystem: Boolean)
|
||||
|
||||
enum class Sort {
|
||||
NAME, PACKAGE, INSTALL_TIME, UPDATE_TIME
|
||||
}
|
||||
|
||||
private var filteredCache: List<AppInfo> = apps
|
||||
|
||||
inner class Holder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val root: View = view.findViewById(R.id.root)
|
||||
val icon: ImageView = view.findViewById(R.id.icon)
|
||||
val label: TextView = view.findViewById(R.id.label)
|
||||
val packageName: TextView = view.findViewById(R.id.packageName)
|
||||
val checkbox: MaterialCheckBox = view.findViewById(R.id.checkbox)
|
||||
}
|
||||
|
||||
suspend fun applyFilter(keyword: String, sort: Sort, decrease: Boolean, systemApp: Boolean) {
|
||||
withContext(Dispatchers.Default) {
|
||||
val newList = apps.parallelStream()
|
||||
.filter {
|
||||
it.label.contains(keyword, true)
|
||||
|| it.packageName.contains(keyword, true)
|
||||
|| systemApp
|
||||
|| it.isSystem
|
||||
}
|
||||
.sorted { a, b ->
|
||||
val result = when (sort) {
|
||||
Sort.NAME -> a.label.compareTo(b.label, true)
|
||||
Sort.PACKAGE -> a.packageName.compareTo(b.packageName)
|
||||
Sort.INSTALL_TIME -> (a.installTime - b.installTime).toInt()
|
||||
Sort.UPDATE_TIME -> (a.updateTime - b.updateTime).toInt()
|
||||
}
|
||||
|
||||
if (decrease) -result else result
|
||||
}
|
||||
.toList()
|
||||
val oldList = filteredCache
|
||||
|
||||
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldList[oldItemPosition].packageName == newList[newItemPosition].packageName
|
||||
}
|
||||
|
||||
override fun getOldListSize(): Int {
|
||||
return oldList.size
|
||||
}
|
||||
|
||||
override fun getNewListSize(): Int {
|
||||
return newList.size
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItemPosition: Int,
|
||||
newItemPosition: Int
|
||||
): Boolean {
|
||||
return areItemsTheSame(oldItemPosition, newItemPosition)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
filteredCache = newList
|
||||
result.dispatchUpdatesTo(this@PackagesAdapter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
|
||||
return Holder(LayoutInflater.from(context).inflate(R.layout.adapter_package, parent, false))
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return filteredCache.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: Holder, position: Int) {
|
||||
val current = filteredCache[position]
|
||||
|
||||
holder.icon.setImageDrawable(current.icon)
|
||||
holder.label.text = current.label
|
||||
holder.packageName.text = current.packageName
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.github.kr328.clash.settings
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import com.github.kr328.clash.BaseActivity
|
||||
import com.github.kr328.clash.OnBootReceiver
|
||||
import com.github.kr328.clash.R
|
||||
import com.github.kr328.clash.remote.Broadcasts
|
||||
import com.github.kr328.clash.service.settings.ServiceSettings
|
||||
import com.github.kr328.clash.service.util.componentName
|
||||
|
||||
@@ -19,6 +22,12 @@ class BehaviorFragment: BaseSettingFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
findPreference(KEY_SHOW_TRAFFIC).isEnabled = !Broadcasts.clashRunning
|
||||
}
|
||||
|
||||
override val xmlResourceId: Int
|
||||
get() = R.xml.settings_behavior
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.github.kr328.clash.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import com.github.kr328.clash.AccessControlPackagesActivity
|
||||
import com.github.kr328.clash.BaseActivity
|
||||
import com.github.kr328.clash.R
|
||||
import com.github.kr328.clash.preference.UiSettings
|
||||
import com.github.kr328.clash.remote.Broadcasts
|
||||
import com.github.kr328.clash.service.settings.ServiceSettings
|
||||
import com.github.kr328.clash.service.util.intent
|
||||
|
||||
class NetworkFragment: BaseSettingFragment() {
|
||||
companion object {
|
||||
@@ -15,13 +18,18 @@ class NetworkFragment: BaseSettingFragment() {
|
||||
private const val KEY_DNS_OVERRIDE = "dns_override"
|
||||
private const val KEY_APPEND_SYS_DNS = "append_system_dns"
|
||||
private const val KEY_ACCESS_CONTROL_MODE = "access_control_mode"
|
||||
private const val KEY_ACCESS_CONTROL_PACKAGES = "access_control_packages"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if ((requireActivity() as BaseActivity).clashRunning)
|
||||
preferenceScreen.isEnabled = false
|
||||
preferenceScreen.isEnabled = !Broadcasts.clashRunning
|
||||
|
||||
findPreference(KEY_ACCESS_CONTROL_PACKAGES).setOnPreferenceClickListener {
|
||||
startActivity(AccessControlPackagesActivity::class.intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDataStore(): SettingsDataStore {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:elevation="4dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/toolbarColor" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/mainList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root"
|
||||
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/icon"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:contentDescription="@string/launch_name" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/icon"
|
||||
android:layout_toStartOf="@id/checkbox"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/packageName"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:focusable="false"
|
||||
android:clickable="false"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:gravity="center"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="70dp" />
|
||||
</RelativeLayout>
|
||||
@@ -40,6 +40,7 @@
|
||||
android:entryValues="@array/access_control_mode_value"
|
||||
android:defaultValue="access_control_mode_all" />
|
||||
<Preference
|
||||
android:key="access_control_packages"
|
||||
android:title="@string/access_control_packages"
|
||||
android:summary="@string/access_control_packages_summary">
|
||||
</Preference>
|
||||
|
||||
@@ -19,7 +19,7 @@ import com.github.kr328.clash.core.utils.asSpeedString
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
|
||||
class ClashNotification(private val context: ClashService, enableRefresh: Boolean) :
|
||||
class ClashNotification(private val context: ClashService, private val enableRefresh: Boolean) :
|
||||
CoroutineScope by context {
|
||||
companion object {
|
||||
private const val CLASH_STATUS_NOTIFICATION_CHANNEL = "clash_status_channel"
|
||||
@@ -63,7 +63,7 @@ class ClashNotification(private val context: ClashService, enableRefresh: Boolea
|
||||
init {
|
||||
createNotificationChannel()
|
||||
|
||||
notifyBaseNotification()
|
||||
updateBase()
|
||||
|
||||
if (enableRefresh) {
|
||||
launch {
|
||||
@@ -119,7 +119,9 @@ class ClashNotification(private val context: ClashService, enableRefresh: Boolea
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
context.unregisterReceiver(observer)
|
||||
if ( enableRefresh )
|
||||
context.unregisterReceiver(observer)
|
||||
|
||||
context.stopForeground(true)
|
||||
}
|
||||
|
||||
@@ -127,11 +129,14 @@ class ClashNotification(private val context: ClashService, enableRefresh: Boolea
|
||||
launch {
|
||||
this@ClashNotification.profile = profile
|
||||
|
||||
update()
|
||||
if ( enableRefresh )
|
||||
update()
|
||||
else
|
||||
updateBase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyBaseNotification() {
|
||||
private fun updateBase() {
|
||||
val notification = baseBuilder
|
||||
.setContentTitle(profile)
|
||||
.setContentText(context.getText(R.string.running))
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
<string name="profile_status_update_failure">更新 %s 失败</string>
|
||||
<string name="profile_status_updating">正在更新 %s</string>
|
||||
<string name="running">正在运行</string>
|
||||
<string name="tun_service_label">Tun 服务</string>
|
||||
<string name="tun_service_label">Tun 设备服务</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user