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