mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-07-03 21:21:12 +08:00
parent
fd61a8178a
commit
ec4cda0a94
@ -79,6 +79,10 @@
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".activities.MainActivity" />
|
||||
<activity
|
||||
android:name=".activities.VpnExceptionsActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".activities.AboutActivity"
|
||||
android:launchMode="singleTop"
|
||||
|
||||
@ -81,7 +81,9 @@ import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@ -413,13 +415,25 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
|
||||
return abortStart();
|
||||
}
|
||||
} else if(mSettings.tls_decryption) {
|
||||
// Exclude the mitm addon traffic in case system-wide decryption is performed
|
||||
// Important: cannot call addDisallowedApplication with addAllowedApplication
|
||||
try {
|
||||
builder.addDisallowedApplication(MitmAPI.PACKAGE_NAME);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
// VPN exceptions
|
||||
Set<String> exceptions = mPrefs.getStringSet(Prefs.PREF_VPN_EXCEPTIONS, new HashSet<>());
|
||||
for(String packageName: exceptions) {
|
||||
try {
|
||||
builder.addDisallowedApplication(packageName);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if(mSettings.tls_decryption) {
|
||||
// Exclude the mitm addon traffic in case system-wide decryption is performed
|
||||
// Important: cannot call addDisallowedApplication with addAllowedApplication
|
||||
try {
|
||||
builder.addDisallowedApplication(MitmAPI.PACKAGE_NAME);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -79,9 +79,11 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
|
||||
@Override
|
||||
public boolean onPreferenceStartFragment(@NonNull PreferenceFragmentCompat caller, @NonNull Preference pref) {
|
||||
PreferenceFragmentCompat targetFragment = null;
|
||||
Log.d(TAG, "startFragment: " + pref.getKey());
|
||||
String prefKey = pref.getKey();
|
||||
|
||||
if(pref.getKey().equals("geolocation")) {
|
||||
Log.d(TAG, "startFragment: " + prefKey);
|
||||
|
||||
if(prefKey.equals("geolocation")) {
|
||||
targetFragment = new GeoipSettings();
|
||||
setTitle(R.string.geolocation);
|
||||
}
|
||||
@ -130,6 +132,7 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
|
||||
private EditTextPreference mSocks5ProxyPort;
|
||||
private DropDownPreference mIpMode;
|
||||
private DropDownPreference mCapInterface;
|
||||
private Preference mVpnExceptions;
|
||||
private SwitchPreference mMalwareDetectionEnabled;
|
||||
private Billing mIab;
|
||||
private boolean mHasStartedMitmWizard;
|
||||
@ -246,7 +249,14 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
|
||||
|
||||
private void setupCapturePrefs() {
|
||||
mCapInterface = requirePreference(Prefs.PREF_CAPTURE_INTERFACE);
|
||||
mVpnExceptions = requirePreference(Prefs.PREF_VPN_EXCEPTIONS);
|
||||
refreshInterfaces();
|
||||
|
||||
mVpnExceptions.setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(requireContext(), VpnExceptionsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setupSecurityPrefs() {
|
||||
@ -391,6 +401,7 @@ public class SettingsActivity extends BaseActivity implements PreferenceFragment
|
||||
|
||||
mIpMode.setVisible(!enabled);
|
||||
mCapInterface.setVisible(enabled);
|
||||
mVpnExceptions.setVisible(!enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of PCAPdroid.
|
||||
*
|
||||
* PCAPdroid 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.
|
||||
*
|
||||
* PCAPdroid is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright 2020-22 - Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.fragments.AppsToggles;
|
||||
import com.emanuelef.remote_capture.model.AppDescriptor;
|
||||
import com.emanuelef.remote_capture.model.Prefs;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class VpnExceptionsActivity extends BaseActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.vpn_exceptions);
|
||||
setContentView(R.layout.fragment_activity);
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragment, new VpnExceptionsFragment())
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static class VpnExceptionsFragment extends AppsToggles {
|
||||
private static final String TAG = "VpnExceptions";
|
||||
private final Set<String> mExcludedApps = new HashSet<>();
|
||||
private @Nullable SharedPreferences mPrefs;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
assert mPrefs != null;
|
||||
|
||||
mExcludedApps.clear();
|
||||
Set<String> saved = mPrefs.getStringSet(Prefs.PREF_VPN_EXCEPTIONS, null);
|
||||
if(saved != null) {
|
||||
Log.d(TAG, "Loading " + saved.size() + " exceptions");
|
||||
mExcludedApps.addAll(saved);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mPrefs = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getCheckedApps() {
|
||||
return mExcludedApps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppToggled(AppDescriptor app, boolean checked) {
|
||||
String packageName = app.getPackageName();
|
||||
if(mExcludedApps.contains(packageName) == checked)
|
||||
return; // nothing to do
|
||||
|
||||
if(checked)
|
||||
mExcludedApps.add(packageName);
|
||||
else
|
||||
mExcludedApps.remove(packageName);
|
||||
|
||||
Log.d(TAG, "Saving " + mExcludedApps.size() + " exceptions");
|
||||
|
||||
if(mPrefs == null)
|
||||
return;
|
||||
|
||||
mPrefs.edit()
|
||||
.putStringSet(Prefs.PREF_VPN_EXCEPTIONS, mExcludedApps)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +53,7 @@ public class AppsAdapter extends RecyclerView.Adapter<AppsAdapter.AppViewHolder>
|
||||
public AppViewHolder(View view) {
|
||||
super(view);
|
||||
|
||||
textInListView = view.findViewById(R.id.list_app_name);
|
||||
textInListView = view.findViewById(R.id.app_name);
|
||||
imageInListView = view.findViewById(R.id.app_icon);
|
||||
packageInListView= view.findViewById(R.id.app_package);
|
||||
}
|
||||
|
||||
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* This file is part of PCAPdroid.
|
||||
*
|
||||
* PCAPdroid 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.
|
||||
*
|
||||
* PCAPdroid is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright 2020-21 - Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture.adapters;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.model.AppDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class AppsTogglesAdapter extends RecyclerView.Adapter<AppsTogglesAdapter.AppViewHolder> {
|
||||
private static final String TAG = "AppToggleAdapter";
|
||||
private final LayoutInflater mLayoutInflater;
|
||||
private final Set<String> mCheckedItems;
|
||||
private AppToggleListener mListener;
|
||||
private List<AppDescriptor> mApps = new ArrayList<>();
|
||||
private @Nullable RecyclerView mRecyclerView;
|
||||
|
||||
public AppsTogglesAdapter(Context context, Set<String> checkedItems) {
|
||||
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mCheckedItems = new HashSet<>(checkedItems);
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
public interface AppToggleListener {
|
||||
void onAppToggled(AppDescriptor app, boolean checked);
|
||||
}
|
||||
|
||||
public static class AppViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView appName;
|
||||
TextView packageName;
|
||||
ImageView icon;
|
||||
SwitchCompat toggle;
|
||||
|
||||
public AppViewHolder(View view) {
|
||||
super(view);
|
||||
|
||||
appName = view.findViewById(R.id.app_name);
|
||||
icon = view.findViewById(R.id.icon);
|
||||
packageName = view.findViewById(R.id.app_package);
|
||||
toggle = view.findViewById(R.id.toggle_btn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onDetachedFromRecyclerView(recyclerView);
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AppsTogglesAdapter.AppViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = mLayoutInflater.inflate(R.layout.app_selection_item, parent, false);
|
||||
AppViewHolder recyclerViewHolder = new AppViewHolder(view);
|
||||
|
||||
view.setOnClickListener((v) -> {
|
||||
if(mRecyclerView != null) {
|
||||
int pos = recyclerViewHolder.getAbsoluteAdapterPosition();
|
||||
boolean checked = mCheckedItems.contains(getItem(pos).getPackageName());
|
||||
handleToggle(pos, !checked);
|
||||
}
|
||||
});
|
||||
|
||||
recyclerViewHolder.toggle.setOnClickListener((v) -> {
|
||||
if(mRecyclerView != null) {
|
||||
int pos = recyclerViewHolder.getAbsoluteAdapterPosition();
|
||||
boolean checked = ((SwitchCompat)v).isChecked();
|
||||
handleToggle(pos, checked);
|
||||
}
|
||||
});
|
||||
|
||||
return(recyclerViewHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AppViewHolder holder, int position) {
|
||||
AppDescriptor app = getItem(position);
|
||||
|
||||
holder.appName.setText(app.getName());
|
||||
holder.packageName.setText(app.getPackageName());
|
||||
holder.toggle.setChecked(mCheckedItems.contains(app.getPackageName()));
|
||||
|
||||
if(app.getIcon() != null)
|
||||
holder.icon.setImageDrawable(app.getIcon());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mApps.size();
|
||||
}
|
||||
|
||||
public AppDescriptor getItem(int pos) {
|
||||
if((pos < 0) || (pos > mApps.size()))
|
||||
return null;
|
||||
|
||||
return mApps.get(pos);
|
||||
}
|
||||
|
||||
private void handleToggle(int old_pos, boolean checked) {
|
||||
AppDescriptor app = getItem(old_pos);
|
||||
String packageName = app.getPackageName();
|
||||
|
||||
if(checked == mCheckedItems.contains(packageName))
|
||||
return; // nothing changed
|
||||
|
||||
if(checked)
|
||||
mCheckedItems.add(packageName);
|
||||
else
|
||||
mCheckedItems.remove(packageName);
|
||||
|
||||
if(mListener != null)
|
||||
mListener.onAppToggled(app, checked);
|
||||
|
||||
// determine the new item position
|
||||
int new_pos = old_pos;
|
||||
for(int i=0; i<mApps.size(); i++) {
|
||||
AppDescriptor other = mApps.get(i);
|
||||
|
||||
if((i != old_pos) && compareCheckedFirst(app, other) <= 0) {
|
||||
new_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(new_pos > old_pos)
|
||||
new_pos--;
|
||||
|
||||
Log.d(TAG, "Item @" + old_pos + ": " + (checked ? "checked" : "unchecked") + " -> " + new_pos);
|
||||
notifyItemChanged(old_pos);
|
||||
|
||||
if(new_pos != old_pos) {
|
||||
mApps.remove(old_pos);
|
||||
mApps.add(new_pos, app);
|
||||
notifyItemMoved(old_pos, new_pos);
|
||||
|
||||
if(mRecyclerView != null) {
|
||||
if(checked)
|
||||
mRecyclerView.scrollToPosition(new_pos);
|
||||
else
|
||||
mRecyclerView.scrollToPosition(old_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort apps so that checked items always appear first
|
||||
private int compareCheckedFirst(AppDescriptor a, AppDescriptor b) {
|
||||
boolean aChecked = mCheckedItems.contains(a.getPackageName());
|
||||
boolean bChecked = mCheckedItems.contains(b.getPackageName());
|
||||
|
||||
if(aChecked && !bChecked)
|
||||
return -1;
|
||||
else if(!aChecked && bChecked)
|
||||
return 1;
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setApps(List<AppDescriptor> apps) {
|
||||
mApps = apps;
|
||||
Collections.sort(mApps, this::compareCheckedFirst);
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setAppToggleListener(final AppToggleListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import com.emanuelef.remote_capture.Billing;
|
||||
import com.emanuelef.remote_capture.CaptureService;
|
||||
import com.emanuelef.remote_capture.ConnectionsRegister;
|
||||
import com.emanuelef.remote_capture.PCAPdroid;
|
||||
@ -84,7 +83,7 @@ public class AppsFragment extends Fragment implements ConnectionsListener {
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
mRecyclerView = view.findViewById(R.id.apps_stats_view);
|
||||
mRecyclerView = view.findViewById(R.id.recycler_view);
|
||||
mRecyclerView.setLayoutManager(new EmptyRecyclerView.MyLinearLayoutManager(getContext()));
|
||||
registerForContextMenu(mRecyclerView);
|
||||
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* This file is part of PCAPdroid.
|
||||
*
|
||||
* PCAPdroid 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.
|
||||
*
|
||||
* PCAPdroid is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright 2020-22 - Emanuele Faranda
|
||||
*/
|
||||
|
||||
package com.emanuelef.remote_capture.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.emanuelef.remote_capture.AppsLoader;
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.adapters.AppsTogglesAdapter;
|
||||
import com.emanuelef.remote_capture.interfaces.AppsLoadListener;
|
||||
import com.emanuelef.remote_capture.model.AppDescriptor;
|
||||
import com.emanuelef.remote_capture.views.EmptyRecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import kotlin.NotImplementedError;
|
||||
|
||||
public abstract class AppsToggles extends Fragment implements AppsLoadListener, AppsTogglesAdapter.AppToggleListener {
|
||||
private AppsTogglesAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
setHasOptionsMenu(true);
|
||||
return inflater.inflate(R.layout.apps_stats, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
EmptyRecyclerView recyclerView = view.findViewById(R.id.recycler_view);
|
||||
recyclerView.setLayoutManager(new EmptyRecyclerView.MyLinearLayoutManager(getContext()));
|
||||
|
||||
mAdapter = new AppsTogglesAdapter(requireContext(), getCheckedApps());
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
mAdapter.setAppToggleListener(this);
|
||||
|
||||
TextView emptyAppsView = view.findViewById(R.id.no_apps);
|
||||
emptyAppsView.setText(R.string.loading_apps);
|
||||
recyclerView.setEmptyView(emptyAppsView);
|
||||
|
||||
(new AppsLoader((AppCompatActivity) requireActivity()))
|
||||
.setAppsLoadListener(this)
|
||||
.loadAllApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppsInfoLoaded(List<AppDescriptor> apps) {
|
||||
mAdapter.setApps(apps);
|
||||
}
|
||||
|
||||
// Must be implemented in sub-classes
|
||||
protected Set<String> getCheckedApps() {
|
||||
throw new NotImplementedError();
|
||||
}
|
||||
}
|
||||
@ -74,6 +74,7 @@ public class Prefs {
|
||||
public static final String PREF_AUTO_BLOCK_PRIVATE_DNS = "auto_block_private_dns";
|
||||
public static final String PREF_APP_VERSION = "appver";
|
||||
public static final String PREF_LOCKDOWN_VPN_NOTICE_SHOWN = "vpn_lockdown_notice";
|
||||
public static final String PREF_VPN_EXCEPTIONS = "vpn_exceptions";
|
||||
|
||||
public enum DumpMode {
|
||||
NONE,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@ -17,21 +18,20 @@
|
||||
android:layout_height="36dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_marginLeft="3dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="10dp">
|
||||
<TextView
|
||||
android:id="@+id/list_app_name"
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:text="App Name"/>
|
||||
tools:text="App Name"/>
|
||||
<TextView
|
||||
android:id="@+id/app_package"
|
||||
android:text="App Package Name"
|
||||
tools:text="App Package Name"
|
||||
android:textSize="12sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
45
app/src/main/res/layout/app_selection_item.xml
Normal file
45
app/src/main/res/layout/app_selection_item.xml
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
tools:src="@drawable/ic_apps"
|
||||
tools:tint="@color/danger"
|
||||
android:layout_margin="4dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="10dp">
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
tools:text="App Name"/>
|
||||
<TextView
|
||||
android:id="@+id/app_package"
|
||||
tools:text="App Package Name"
|
||||
android:textSize="12sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/toggle_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|end"/>
|
||||
</LinearLayout>
|
||||
@ -4,7 +4,7 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.emanuelef.remote_capture.views.EmptyRecyclerView
|
||||
android:id="@+id/apps_stats_view"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
|
||||
@ -372,4 +372,6 @@
|
||||
<string name="ipv6_only">IPv6 only</string>
|
||||
<string name="ip_both">IPv4 and IPv6</string>
|
||||
<string name="notifications_notice">The app uses notifications to send alerts in case of anomalous events. Grant it the permission to send notifications in the next screen</string>
|
||||
<string name="vpn_exceptions">VPN Exceptions</string>
|
||||
<string name="vpn_exceptions_summary">Exclude some apps from the VPN connection. Their traffic will not be monitored</string>
|
||||
</resources>
|
||||
|
||||
@ -117,6 +117,13 @@
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue="\@inet" />
|
||||
|
||||
<Preference
|
||||
android:key="vpn_exceptions"
|
||||
app:title="@string/vpn_exceptions"
|
||||
app:summary="@string/vpn_exceptions_summary"
|
||||
app:iconSpaceReserved="false"
|
||||
app:fragment="com.emanuelef.remote_capture.fragments.VpnExceptions" />
|
||||
|
||||
<SwitchPreference
|
||||
app:key="start_at_boot"
|
||||
android:title="@string/start_at_boot"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user