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"/> + + + + + +