diff --git a/app/build.gradle b/app/build.gradle
index 41c32f9..98af244 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -44,6 +44,7 @@ dependencies {
implementation 'androidx.percentlayout:percentlayout:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha03'
//DNS
implementation 'org.pcap4j:pcap4j-core:1.7.4'
implementation 'org.pcap4j:pcap4j-packetfactory-static:1.7.4'
@@ -51,8 +52,8 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
//Analytics
- implementation 'com.google.firebase:firebase-core:16.0.6'
- implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
+ implementation 'com.google.firebase:firebase-core:16.0.8'
+ implementation 'com.crashlytics.sdk.android:crashlytics:2.9.9'
}
apply plugin: 'com.google.gms.google-services'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bd26748..1979a6f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -71,6 +71,7 @@
android:configChanges="keyboard|keyboardHidden|screenLayout|uiMode|orientation|screenSize|smallestScreenSize"
android:theme="@style/AppTheme.NoActionBar">
+
\ No newline at end of file
diff --git a/app/src/main/java/org/itxtech/daedalus/activity/FilterAppProxyActivity.java b/app/src/main/java/org/itxtech/daedalus/activity/FilterAppProxyActivity.java
new file mode 100644
index 0000000..65ee105
--- /dev/null
+++ b/app/src/main/java/org/itxtech/daedalus/activity/FilterAppProxyActivity.java
@@ -0,0 +1,195 @@
+package org.itxtech.daedalus.activity;
+
+import android.annotation.SuppressLint;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.itxtech.daedalus.Daedalus;
+import org.itxtech.daedalus.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Daedalus Project
+ *
+ * @author iTX Technologies
+ * @link https://itxtech.org
+ *
+ * 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.
+ */
+public class FilterAppProxyActivity extends AppCompatActivity {
+
+ private RecyclerViewAdapter adapter;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ if (Daedalus.isDarkTheme()) {
+ setTheme(R.style.AppTheme_Dark_NoActionBar);
+ }
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_filter_app);
+ Toolbar toolbar = findViewById(R.id.toolbar_filter);
+ Drawable drawable = ContextCompat.getDrawable(this, R.drawable.ic_clear);
+ RecyclerView recyclerView = findViewById(R.id.recyclerView_app_filter_list);
+ LinearLayoutManager manager = new LinearLayoutManager(this);
+ recyclerView.setLayoutManager(manager);
+ Drawable wrappedDrawable = DrawableCompat.wrap(Objects.requireNonNull(drawable));
+ DrawableCompat.setTint(wrappedDrawable, Color.WHITE);
+ toolbar.setNavigationIcon(drawable);
+ toolbar.setNavigationOnClickListener(v -> onBackPressed());
+ adapter = new RecyclerViewAdapter(getAppList());
+ recyclerView.setAdapter(adapter);
+ }
+
+ @Override
+ public void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ Toolbar toolbar = findViewById(R.id.toolbar_filter);
+ toolbar.setTitle(R.string.settings_app_filter);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ Daedalus.configurations.save();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ adapter.notifyDataSetChanged();
+ }
+
+ private class AppObject {
+ private String app_name;
+ private String app_package_name;
+ private Drawable app_icon;
+
+ AppObject(String appName, String packageName, Drawable appIcon) {
+ this.app_name = appName;
+ this.app_package_name = packageName;
+ this.app_icon = appIcon;
+ }
+ }
+
+ private ArrayList getAppList() {
+ PackageManager packageManager = getBaseContext().getPackageManager();
+ List packages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+ ArrayList appObjects = new ArrayList<>();
+ for (PackageInfo pkg : packages) {
+ appObjects.add(new AppObject(
+ pkg.applicationInfo.loadLabel(packageManager).toString(),
+ pkg.packageName,
+ pkg.applicationInfo.loadIcon(packageManager)
+ ));
+ }
+ return appObjects;
+ }
+
+ private class RecyclerViewAdapter extends RecyclerView.Adapter {
+ private ArrayList appList;
+ @SuppressLint("UseSparseArrays")
+ private Map check_status = new HashMap<>();
+
+ RecyclerViewAdapter(ArrayList appObjects) {
+ appList = appObjects;
+
+ for (int i = 0; i < appObjects.size(); i++) {
+ if (Daedalus.configurations.getFilterAppObjects().contains(appObjects.get(i).app_package_name)) {
+ check_status.put(i, true);
+ }
+ }
+ }
+
+
+ @NonNull
+ @Override
+ public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_appview, parent, false);
+ return new RecyclerViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
+ String package_name = appList.get(position).app_package_name;
+ holder.app_name.setText(appList.get(position).app_name);
+ holder.app_icon.setImageDrawable(appList.get(position).app_icon);
+ holder.app_package_name = package_name;
+ holder.app_check.setOnCheckedChangeListener(null);
+ if (Daedalus.configurations.getFilterAppObjects().contains(package_name)) {
+ holder.app_check.setChecked(true);
+ //check_status.put(position, true);
+ }
+ holder.app_check.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ check_status.put(position, true);
+ } else {
+ check_status.remove(position);
+ }
+ });
+ if (check_status != null && check_status.containsKey(position)) {
+ holder.app_check.setChecked(true);
+ } else {
+ holder.app_check.setChecked(false);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return appList.size();
+ }
+ }
+
+ private class RecyclerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ private ImageView app_icon;
+ private TextView app_name;
+ private CheckBox app_check;
+ private String app_package_name;
+
+ RecyclerViewHolder(@NonNull View itemView) {
+ super(itemView);
+ app_icon = itemView.findViewById(R.id.app_icon);
+ app_name = itemView.findViewById(R.id.app_name);
+ app_check = itemView.findViewById(R.id.app_check);
+ itemView.setOnClickListener(this);
+ }
+
+
+ @Override
+ public void onClick(View v) {
+ if (app_check.isChecked()) {
+ app_check.setChecked(false);
+ Daedalus.configurations.getFilterAppObjects().remove(app_package_name);
+ } else {
+ app_check.setChecked(true);
+ Daedalus.configurations.getFilterAppObjects().add(app_package_name);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/itxtech/daedalus/fragment/GlobalConfigFragment.java b/app/src/main/java/org/itxtech/daedalus/fragment/GlobalConfigFragment.java
index 47aea8d..849c88b 100644
--- a/app/src/main/java/org/itxtech/daedalus/fragment/GlobalConfigFragment.java
+++ b/app/src/main/java/org/itxtech/daedalus/fragment/GlobalConfigFragment.java
@@ -5,9 +5,12 @@ import android.os.Build;
import android.os.Bundle;
import android.preference.*;
import android.view.View;
+
import com.google.android.material.snackbar.Snackbar;
+
import org.itxtech.daedalus.Daedalus;
import org.itxtech.daedalus.R;
+import org.itxtech.daedalus.activity.FilterAppProxyActivity;
import org.itxtech.daedalus.activity.MainActivity;
import org.itxtech.daedalus.util.server.DNSServerHelper;
@@ -82,14 +85,28 @@ public class GlobalConfigFragment extends PreferenceFragment {
SwitchPreference boot = (SwitchPreference) findPreference("settings_boot");
boot.setEnabled(false);
boot.setChecked(false);
+ SwitchPreference app_filter = (SwitchPreference) findPreference("settings_app_filter_switch");
+ app_filter.setEnabled(false);
+ app_filter.setChecked(false);
}
SwitchPreference advanced = (SwitchPreference) findPreference("settings_advanced_switch");
advanced.setOnPreferenceChangeListener((preference, newValue) -> {
- updateAdvancedOptions((boolean) newValue);
+ updateOptions((boolean) newValue, "settings_advanced");
return true;
});
+ SwitchPreference app_filter = (SwitchPreference) findPreference("settings_app_filter_switch");
+ app_filter.setOnPreferenceChangeListener((p, w) -> {
+ updateOptions((boolean) w, "settings_app_filter");
+ return true;
+ });
+
+ findPreference("settings_app_filter_list").setOnPreferenceClickListener(preference -> {
+ startActivity(new Intent(getActivity(), FilterAppProxyActivity.class));
+ return false;
+ });
+
findPreference("settings_check_update").setOnPreferenceClickListener(preference -> {
Daedalus.openUri("https://github.com/iTXTech/Daedalus/releases");
return false;
@@ -110,11 +127,12 @@ public class GlobalConfigFragment extends PreferenceFragment {
return false;
});
- updateAdvancedOptions(advanced.isChecked());
+ updateOptions(advanced.isChecked(), "settings_advanced");
+ updateOptions(app_filter.isChecked(), "settings_app_filter");
}
- private void updateAdvancedOptions(boolean checked) {
- PreferenceCategory category = (PreferenceCategory) findPreference("settings_advanced");
+ private void updateOptions(boolean checked, String pref) {
+ PreferenceCategory category = (PreferenceCategory) findPreference(pref);
for (int i = 1; i < category.getPreferenceCount(); i++) {
Preference preference = category.getPreference(i);
if (checked) {
diff --git a/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java b/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java
index 29b78f7..5fd4a53 100644
--- a/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java
+++ b/app/src/main/java/org/itxtech/daedalus/service/DaedalusVpnService.java
@@ -7,12 +7,15 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.VpnService;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.system.OsConstants;
import android.util.Log;
+
import androidx.core.app.NotificationCompat;
+
import org.itxtech.daedalus.Daedalus;
import org.itxtech.daedalus.R;
import org.itxtech.daedalus.activity.MainActivity;
@@ -28,6 +31,7 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
+import java.util.Set;
/**
* Daedalus Project
@@ -238,7 +242,28 @@ public class DaedalusVpnService extends VpnService implements Runnable {
.setConfigureIntent(PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class).putExtra(MainActivity.LAUNCH_FRAGMENT, MainActivity.FRAGMENT_SETTINGS),
PendingIntent.FLAG_ONE_SHOT));
+
+ //Set App Filter
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && Daedalus.getPrefs().getBoolean("settings_app_filter_switch", false)) {
+ Set apps = Daedalus.getPrefs().getStringSet("filterAppObjects", null);
+ if (apps != null) {
+ boolean mode = Daedalus.getPrefs().getBoolean("settings_app_filter_mode_switch", false);
+ for (String app : apps) {
+ try {
+ if (mode) {
+ builder.addDisallowedApplication(app);
+ } else {
+ builder.addAllowedApplication(app);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Logger.error("Package Not Found:" + app);
+ }
+ }
+ }
+ }
+
String format = null;
+
for (String prefix : new String[]{"10.0.0", "192.0.2", "198.51.100", "203.0.113", "192.168.50"}) {
try {
builder.addAddress(prefix + ".1", 24);
@@ -303,8 +328,10 @@ public class DaedalusVpnService extends VpnService implements Runnable {
Thread.sleep(1000);
}
}
- } catch (InterruptedException ignored) {
- } catch (Exception e) {
+ } catch (
+ InterruptedException ignored) {
+ } catch (
+ Exception e) {
Logger.logException(e);
} finally {
Log.d(TAG, "quit");
diff --git a/app/src/main/java/org/itxtech/daedalus/util/Configurations.java b/app/src/main/java/org/itxtech/daedalus/util/Configurations.java
index 25cd8b6..97afd53 100644
--- a/app/src/main/java/org/itxtech/daedalus/util/Configurations.java
+++ b/app/src/main/java/org/itxtech/daedalus/util/Configurations.java
@@ -2,6 +2,7 @@ package org.itxtech.daedalus.util;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
+
import org.itxtech.daedalus.Daedalus;
import org.itxtech.daedalus.util.server.CustomDNSServer;
@@ -28,6 +29,7 @@ public class Configurations {
private static File file;
private ArrayList customDNSServers;
+ private ArrayList filterAppObjects;
private ArrayList hostsRules;
private ArrayList dnsmasqRules;
@@ -66,6 +68,13 @@ public class Configurations {
return customDNSServers;
}
+ public ArrayList getFilterAppObjects() {
+ if (filterAppObjects == null) {
+ filterAppObjects = new ArrayList<>();
+ }
+ return filterAppObjects;
+ }
+
public ArrayList getHostsRules() {
if (hostsRules == null) {
hostsRules = new ArrayList<>();
diff --git a/app/src/main/res/layout/activity_filter_app.xml b/app/src/main/res/layout/activity_filter_app.xml
new file mode 100644
index 0000000..0668267
--- /dev/null
+++ b/app/src/main/res/layout/activity_filter_app.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/card_appview.xml b/app/src/main/res/layout/card_appview.xml
new file mode 100644
index 0000000..a41bd15
--- /dev/null
+++ b/app/src/main/res/layout/card_appview.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 0535f82..f0fe096 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -83,6 +83,12 @@
服务器地址
服务器端口
+ 分应用代理
+ 选择应用
+ 黑名单模式
+ 选择要过滤的应用
+ 仅支持Android Lollipop及以上版本
+
PdoMo DNS 主服务器
PdoMo DNS 辅服务器
PureDNS 华南
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 774fa36..2217f41 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -83,6 +83,12 @@
伺服器網址
伺服器埠
+ 分應用代理
+ 選擇應用
+ 黑名單模式
+ 選擇要過濾的應用
+ 僅支持Android Lollipop及以上版本
+
PdoMo DNS 主伺服器
PdoMo DNS 輔伺服器
PureDNS 華南
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 51b48fb..4612fd6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -88,6 +88,12 @@
Server Address
Server Port
+ App Filter
+ Blacklist mode
+ Select application
+ Select an app to filter
+ Only supports Android Lollipop and above
+
PdoMo DNS Primary
PdoMo DNS Secondary
PureDNS South China
diff --git a/app/src/main/res/xml/perf_settings.xml b/app/src/main/res/xml/perf_settings.xml
index cf28622..0e2c78a 100644
--- a/app/src/main/res/xml/perf_settings.xml
+++ b/app/src/main/res/xml/perf_settings.xml
@@ -48,6 +48,26 @@
android:title="@string/settings_log_size"/>
+
+
+
+
+
+