From eb295f1c04eab6770572d3aee731d03079005dfc Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Thu, 4 Nov 2021 19:02:30 +0100 Subject: [PATCH] Improve CaptureCtrl - The app theme is now honored - The caller app name and icon is now shown in the request dialog - It's now possible to grant/deny control requests via permissions - Control permissions rules can be deleted from the settings activity Needed for #138 --- app/src/main/AndroidManifest.xml | 3 + .../emanuelef/remote_capture/PCAPdroid.java | 10 +- .../activities/CaptureCtrl.java | 102 ++++++++++-- .../activities/EditCtrlPermissions.java | 128 +++++++++++++++ .../activities/EditListActivity.java | 2 +- .../activities/SettingsActivity.java | 7 + .../adapters/CtrlPermissionsAdapter.java | 90 +++++++++++ .../remote_capture/model/CtrlPermissions.java | 149 ++++++++++++++++++ app/src/main/res/layout/ctrl_consent.xml | 75 ++++++++- .../main/res/layout/edit_list_activity.xml | 12 -- app/src/main/res/values-it/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values/strings.xml | 9 +- app/src/main/res/xml/root_preferences.xml | 7 + 16 files changed, 567 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/com/emanuelef/remote_capture/activities/EditCtrlPermissions.java create mode 100644 app/src/main/java/com/emanuelef/remote_capture/adapters/CtrlPermissionsAdapter.java create mode 100644 app/src/main/java/com/emanuelef/remote_capture/model/CtrlPermissions.java delete mode 100644 app/src/main/res/layout/edit_list_activity.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c01757e5..28c65089 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,9 @@ android:theme="@style/AppTheme.Floating" android:launchMode="singleTop" android:exported="true" /> + diff --git a/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java b/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java index 7a8af313..b4320d7e 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java +++ b/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java @@ -27,6 +27,7 @@ import android.content.res.Resources; import androidx.preference.PreferenceManager; import com.emanuelef.remote_capture.model.Blacklists; +import com.emanuelef.remote_capture.model.CtrlPermissions; import com.emanuelef.remote_capture.model.MatchList; import com.emanuelef.remote_capture.model.Prefs; @@ -38,6 +39,7 @@ public class PCAPdroid extends Application { private MatchList mVisMask; private MatchList mMalwareWhitelist; private Blacklists mBlacklists; + private CtrlPermissions mCtrlPermissions; private Context mLocalizedContext; private static WeakReference mInstance; @@ -79,14 +81,18 @@ public class PCAPdroid extends Application { public Blacklists getBlacklistsStatus() { if(mBlacklists == null) mBlacklists = new Blacklists(mLocalizedContext); - return mBlacklists; } public MatchList getMalwareWhitelist() { if(mMalwareWhitelist == null) mMalwareWhitelist = new MatchList(mLocalizedContext, Prefs.PREF_MALWARE_WHITELIST); - return mMalwareWhitelist; } + + public CtrlPermissions getCtrlPermissions() { + if(mCtrlPermissions == null) + mCtrlPermissions = new CtrlPermissions(this); + return mCtrlPermissions; + } } \ No newline at end of file diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java b/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java index de886e6c..aed8bfa9 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/CaptureCtrl.java @@ -1,3 +1,22 @@ +/* + * 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 . + * + * Copyright 2020-21 - Emanuele Faranda + */ + package com.emanuelef.remote_capture.activities; import android.content.Intent; @@ -5,24 +24,35 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; +import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; +import android.widget.ImageView; +import android.widget.RadioButton; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import com.emanuelef.remote_capture.AppsResolver; import com.emanuelef.remote_capture.CaptureHelper; import com.emanuelef.remote_capture.CaptureService; +import com.emanuelef.remote_capture.PCAPdroid; import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.Utils; +import com.emanuelef.remote_capture.model.AppDescriptor; import com.emanuelef.remote_capture.model.CaptureSettings; +import com.emanuelef.remote_capture.model.CtrlPermissions; public class CaptureCtrl extends AppCompatActivity { + public static final String ACTION_START = "start"; + public static final String ACTION_STOP = "stop"; private static final String TAG = "CaptureCtrl"; - private static String calling_package = null; + private static AppDescriptor mStarterApp = null; // the app which started the capture, may be unknown private CaptureHelper mCapHelper; + private CtrlPermissions mPermissions; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -46,7 +76,22 @@ public class CaptureCtrl extends AppCompatActivity { return; } - if(isAlreadyAuthorized(action)) { + // Check if a control permission rule was set + mPermissions = PCAPdroid.getInstance().getCtrlPermissions(); + AppDescriptor app = getCallingApp(); + if(app != null) { + CtrlPermissions.ConsentType consent = mPermissions.getConsent(app.getPackageName()); + + if(consent == CtrlPermissions.ConsentType.ALLOW) { + processRequest(intent, action); + return; + } else if(consent == CtrlPermissions.ConsentType.DENY) { + abort(); + return; + } + } + + if(isControlApp(action)) { processRequest(intent, action); return; } @@ -54,10 +99,18 @@ public class CaptureCtrl extends AppCompatActivity { // Show authorization window setContentView(R.layout.ctrl_consent); findViewById(R.id.allow_btn).setOnClickListener(v -> { - Utils.showToast(this, R.string.ctrl_consent_allowed); - processRequest(intent, action); + controlAction(intent, action, true); }); - findViewById(R.id.deny_btn).setOnClickListener(v -> abort()); + findViewById(R.id.deny_btn).setOnClickListener(v -> { + controlAction(intent, action, false); + }); + + if(app != null) { + ((TextView)findViewById(R.id.app_name)).setText(app.getName()); + ((TextView)findViewById(R.id.app_package)).setText(app.getPackageName()); + ((ImageView)findViewById(R.id.app_icon)).setImageDrawable(app.getIcon()); + } else + findViewById(R.id.caller_app).setVisibility(View.GONE); new Handler(Looper.getMainLooper()).postDelayed(() -> { Button btn = findViewById(R.id.allow_btn); @@ -66,16 +119,37 @@ public class CaptureCtrl extends AppCompatActivity { }, 1500); } + private AppDescriptor getCallingApp() { + String callp = getCallingPackage(); + return (callp != null) ? AppsResolver.resolve(getPackageManager(), callp, 0) : null; + } + + private void controlAction(Intent intent, String action, boolean allow) { + AppDescriptor app = getCallingApp(); + if(app != null) { + boolean is_forever = ((RadioButton)findViewById(R.id.choice_forever)).isChecked(); + if(is_forever) { + Log.d(TAG, (allow ? "Grant" : "Deny") + " forever to " + app.getPackageName()); + mPermissions.add(app.getPackageName(), allow ? CtrlPermissions.ConsentType.ALLOW : CtrlPermissions.ConsentType.DENY); + } + } + + if(!allow) + abort(); + else + processRequest(intent, action); + } + @Override protected void onDestroy() { mCapHelper = null; super.onDestroy(); } - private boolean isAlreadyAuthorized(@NonNull String action) { + private boolean isControlApp(@NonNull String action) { // Automatically authorize an app to stop the capture it started - return !action.equals("start") && (calling_package != null) - && (calling_package.equals(getCallingPackage())); + return !action.equals(ACTION_START) && (mStarterApp != null) + && (mStarterApp.getPackageName().equals(getCallingPackage())); } @Override @@ -96,18 +170,20 @@ public class CaptureCtrl extends AppCompatActivity { } private void processRequest(Intent req_intent, @NonNull String action) { - if(action.equals("start")) { - calling_package = getCallingPackage(); - Log.d(TAG, "Starting capture, caller=" + calling_package); + Utils.showToast(this, R.string.ctrl_consent_allowed); + + if(action.equals(ACTION_START)) { + mStarterApp = getCallingApp(); + Log.d(TAG, "Starting capture, caller=" + mStarterApp); // will call the mCapHelper listener mCapHelper.startCapture(new CaptureSettings(req_intent)); return; - } else if(action.equals("stop")) { + } else if(action.equals(ACTION_STOP)) { Log.d(TAG, "Stopping capture"); CaptureService.stopService(); - calling_package = null; + mStarterApp = null; } else { Log.e(TAG, "unknown action: " + action); abort(); diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/EditCtrlPermissions.java b/app/src/main/java/com/emanuelef/remote_capture/activities/EditCtrlPermissions.java new file mode 100644 index 00000000..2e699cc6 --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/EditCtrlPermissions.java @@ -0,0 +1,128 @@ +/* + * 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 . + * + * Copyright 2020-21 - Emanuele Faranda + */ + +package com.emanuelef.remote_capture.activities; +import android.os.Bundle; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AbsListView; +import android.widget.ListView; +import android.widget.TextView; + +import com.emanuelef.remote_capture.PCAPdroid; +import com.emanuelef.remote_capture.R; +import com.emanuelef.remote_capture.adapters.CtrlPermissionsAdapter; +import com.emanuelef.remote_capture.model.CtrlPermissions; + +import java.util.ArrayList; + +public class EditCtrlPermissions extends BaseActivity { + private static final String TAG = "EditCtrlPermissions"; + private TextView mEmptyText; + private CtrlPermissionsAdapter mAdapter; + private ListView mListView; + private CtrlPermissions mPermissions; + private final ArrayList mSelected = new ArrayList<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTitle(R.string.control_permissions); + setContentView(R.layout.edit_list_fragment); + + mEmptyText = findViewById(R.id.list_empty); + mListView = findViewById(R.id.listview); + + mPermissions = PCAPdroid.getInstance().getCtrlPermissions(); + mAdapter = new CtrlPermissionsAdapter(this, mPermissions); + mListView.setAdapter(mAdapter); + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.list_edit_cab, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + int id = item.getItemId(); + + if(id == R.id.delete_entry) { + if(mSelected.size() >= mAdapter.getCount()) { + mAdapter.clear(); + mPermissions.removeAll(); + } else { + for(CtrlPermissions.Rule rule : mSelected) { + mAdapter.remove(rule); + mPermissions.remove(rule.package_name); + } + } + + mode.finish(); + recheckListSize(); + return true; + } else if(id == R.id.select_all) { + if(mSelected.size() >= mAdapter.getCount()) + mode.finish(); + else { + for(int i=0; i { + Intent intent = new Intent(requireContext(), EditCtrlPermissions.class); + startActivity(intent); + return true; + }); } private void rootCaptureHideShow(boolean enabled) { diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/CtrlPermissionsAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/CtrlPermissionsAdapter.java new file mode 100644 index 00000000..c5865d8b --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/CtrlPermissionsAdapter.java @@ -0,0 +1,90 @@ +/* + * 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 . + * + * Copyright 2020-21 - Emanuele Faranda + */ + +package com.emanuelef.remote_capture.adapters; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.emanuelef.remote_capture.AppsResolver; +import com.emanuelef.remote_capture.R; +import com.emanuelef.remote_capture.interfaces.TextAdapter; +import com.emanuelef.remote_capture.model.AppDescriptor; +import com.emanuelef.remote_capture.model.CtrlPermissions; + +import java.util.HashMap; +import java.util.Iterator; + +public class CtrlPermissionsAdapter extends ArrayAdapter implements TextAdapter { + private final LayoutInflater mLayoutInflater; + private final CtrlPermissions mPermissions; + private final Context mContext; + private final HashMap mPkgToApp = new HashMap<>(); + + public CtrlPermissionsAdapter(Context context, CtrlPermissions perms) { + super(context, R.layout.rule_item); + mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mPermissions = perms; + mContext = context; + load(); + } + + private void load() { + PackageManager pm = mContext.getPackageManager(); + Iterator it = mPermissions.iterRules(); + + while(it.hasNext()) { + CtrlPermissions.Rule rule = it.next(); + AppDescriptor app = AppsResolver.resolve(pm, rule.package_name, 0); + if(app != null) + mPkgToApp.put(rule.package_name, app); + add(rule); + } + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + if(convertView == null) + convertView = mLayoutInflater.inflate(R.layout.rule_item, parent, false); + + CtrlPermissions.Rule rule = getItem(position); + AppDescriptor app = mPkgToApp.get(rule.package_name); // may be null + String text = String.format(mContext.getString(R.string.control_permissions_item), + (app == null) ? rule.package_name : String.format("%s (%s)", app.getName(), app.getPackageName()), + mContext.getString((rule.consent == CtrlPermissions.ConsentType.ALLOW) ? R.string.allow : R.string.deny)); + + ((TextView)convertView.findViewById(R.id.item_label)).setText(text); + + return convertView; + } + + @Override + public String getItemText(int pos) { + return getItem(pos).package_name; + } +} diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/CtrlPermissions.java b/app/src/main/java/com/emanuelef/remote_capture/model/CtrlPermissions.java new file mode 100644 index 00000000..ba923a33 --- /dev/null +++ b/app/src/main/java/com/emanuelef/remote_capture/model/CtrlPermissions.java @@ -0,0 +1,149 @@ +/* + * 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 . + * + * Copyright 2020-21 - Emanuele Faranda + */ + +package com.emanuelef.remote_capture.model; + +import android.content.Context; +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class CtrlPermissions { + private static final String PREF_NAME = "ctrl_perms"; + private final HashMap mRules = new HashMap<>(); + private final SharedPreferences mPrefs; + + public enum ConsentType { + UNSPECIFIED, + ALLOW, + DENY, + }; + + public class Rule { + public final String package_name; + public final ConsentType consent; + + public Rule(String _package_name, ConsentType tp) { + package_name = _package_name; + consent = tp; + } + } + + public CtrlPermissions(Context ctx) { + mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx); + reload(); + } + + public void reload() { + String serialized = mPrefs.getString(PREF_NAME, ""); + //Log.d(TAG, serialized); + + if(!serialized.isEmpty()) { + JsonObject obj = JsonParser.parseString(serialized).getAsJsonObject(); + deserialize(obj); + } else + mRules.clear(); + } + + private void deserialize(JsonObject object) { + mRules.clear(); + + JsonObject rules = object.getAsJsonObject("rules"); + if(rules == null) + return; + + for(Map.Entry rule: rules.entrySet()) { + if(rule.getValue().isJsonPrimitive() && rule.getValue().getAsJsonPrimitive().isString()) { + String val = rule.getValue().getAsJsonPrimitive().getAsString(); + + try { + ConsentType tp = ConsentType.valueOf(val); + mRules.put(rule.getKey(), new Rule(rule.getKey(), tp)); + } catch (IllegalArgumentException ignored) {} + } + } + } + + private static class Serializer implements JsonSerializer { + @Override + public JsonElement serialize(CtrlPermissions src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + JsonObject rulesObj = new JsonObject(); + + for(Rule rule: src.mRules.values()) { + rulesObj.add(rule.package_name, new JsonPrimitive(rule.consent.toString())); + } + + result.add("rules", rulesObj); + return result; + } + } + + private void save() { + Gson gson = new GsonBuilder().registerTypeAdapter(getClass(), new Serializer()) + .create(); + + String serialized = gson.toJson(this); + //Log.d(TAG, "json: " + serialized); + + mPrefs.edit() + .putString(PREF_NAME, serialized) + .apply(); + } + + public void add(String package_name, ConsentType tp) { + mRules.put(package_name, new Rule(package_name, tp)); + save(); + } + + public void remove(String package_name) { + mRules.remove(package_name); + save(); + } + + public void removeAll() { + mRules.clear(); + save(); + } + + public Iterator iterRules() { + return mRules.values().iterator(); + } + + public ConsentType getConsent(String package_name) { + Rule rule = mRules.get(package_name); + if(rule == null) + return ConsentType.UNSPECIFIED; + return rule.consent; + } +} diff --git a/app/src/main/res/layout/ctrl_consent.xml b/app/src/main/res/layout/ctrl_consent.xml index 618ff1b1..7f7cea8e 100644 --- a/app/src/main/res/layout/ctrl_consent.xml +++ b/app/src/main/res/layout/ctrl_consent.xml @@ -1,7 +1,8 @@ - + + + + + + + + + + + + + + + + + + + + + - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 92fda87d..cdd0138b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -144,8 +144,8 @@ Nega Permetti Richiesta di Controllo PCAPdroid - La richiesta di controllo è stata rifiutata - La richiesta di controllo è stata accettata + PCAPdroid: la richiesta di controllo è stata rifiutata + PCAPdroid: la richiesta di controllo è stata accettata Interfaccia di Cattura Internet Tutte le Interfacce diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2d2da0d8..943c267f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -146,8 +146,8 @@ Negar Permitir Pedido de controle do PCAPdroid - O pedido de controle foi negado - O pedido de controle foi permitido + PCAPdroid: o pedido de controle foi negado + PCAPdroid: o pedido de controle foi permitido Interface de captura Internet Todas as Interfaces diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 31d8c81c..89e9e12d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -145,8 +145,8 @@ Отклонить Разрешить PCAPdroid Контрольный запрос - Запрос был отклонен - Запрос был разрешен + PCAPdroid: Запрос был отклонен + PCAPdroid: Запрос был разрешен Интерфейс для захвата Интернет Все интерфейсы diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9ac653ed..2bfe1afa 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -155,8 +155,8 @@ 拒绝 允许 PCAPdroid 控制请求 - 控制请求被拒绝 - 控制请求被允许 + PCAPdroid: 控制请求被拒绝 + PCAPdroid: 控制请求被允许 捕获接口 互联网 所有接口 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c727fb6a..e867af33 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -155,8 +155,8 @@ Deny Allow PCAPdroid Control Request - The control request was denied - The control request was allowed + PCAPdroid: the control request was denied + PCAPdroid: the control request was allowed Capture Interface Internet All Interfaces @@ -190,5 +190,10 @@ Meet the PCAPdroid malware detection! Show me Hint + Once + Forever + Control Permissions + Check which apps are allowed to control the PCAPdroid capture. + %1$s: %2$s diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 48af3601..b1cf45d6 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -140,5 +140,12 @@ app:iconSpaceReserved="false" app:summary="@string/enable_ipv6_summary" app:defaultValue="false" /> + + +