1
0
Fork 0
mirror of https://github.com/M66B/NetGuard.git synced 2025-01-01 12:54:07 +00:00

Block/allow hosts UI

This commit is contained in:
M66B 2016-01-30 14:26:30 +01:00
parent c7bd5292a8
commit 46fd086337
17 changed files with 364 additions and 56 deletions

View file

@ -98,7 +98,6 @@
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/objectFiles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard-rules" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />

View file

@ -1,12 +1,31 @@
package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.text.SimpleDateFormat;
@ -42,13 +61,15 @@ public class AccessAdapter extends CursorAdapter {
// Get views
TextView tvTime = (TextView) view.findViewById(R.id.tvTime);
CheckBox cbBlock = (CheckBox) view.findViewById(R.id.cbBlock);
ImageView ivBlock = (ImageView) view.findViewById(R.id.ivBlock);
final TextView tvDest = (TextView) view.findViewById(R.id.tvDest);
// Set values
tvTime.setText(new SimpleDateFormat("HH:mm:ss").format(time));
cbBlock.setVisibility(block < 0 ? View.INVISIBLE : View.VISIBLE);
cbBlock.setChecked(block > 0);
if (block < 0)
ivBlock.setImageDrawable(null);
else
ivBlock.setImageResource(block > 0 ? R.drawable.host_blocked : R.drawable.host_allowed);
tvDest.setText(daddr + (dport > 0 ? ":" + dport : ""));
}
}

View file

@ -363,7 +363,7 @@ public class ActivityLog extends AppCompatActivity implements SharedPreferences.
new AsyncTask<Object, Object, Object>() {
@Override
protected Object doInBackground(Object... objects) {
dh.clear();
dh.clearLog();
if (prefs.getBoolean("pcap", false)) {
SinkholeService.setPcap(null, false);
pcap_file.delete();

View file

@ -1,5 +1,24 @@
package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -9,7 +28,6 @@ import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class DatabaseHelper extends SQLiteOpenHelper {
@ -19,8 +37,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private static final int DB_VERSION = 11;
private static boolean once = true;
private static List<LogChangedListener> logChangedListeners = new ArrayList<LogChangedListener>();
private static List<AccessChangedListener> accessChangedListeners = new ArrayList<AccessChangedListener>();
private static List<LogChangedListener> logChangedListeners = new ArrayList<>();
private static List<AccessChangedListener> accessChangedListeners = new ArrayList<>();
private Context mContext;
@ -229,7 +247,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return this;
}
public DatabaseHelper clear() {
public DatabaseHelper clearLog() {
synchronized (mContext.getApplicationContext()) {
SQLiteDatabase db = this.getReadableDatabase();
db.delete("log", null, new String[]{});
@ -248,14 +266,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
public Cursor getLog() {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, * FROM log";
String query = "SELECT ID AS _id, *";
query += " FROM log";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{});
}
public Cursor searchLog(String find) {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, * FROM log";
String query = "SELECT ID AS _id, *";
query += " FROM log";
query += " WHERE daddr LIKE ? OR uid LIKE ?";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{"%" + find + "%", "%" + find + "%"});
@ -297,13 +317,56 @@ public class DatabaseHelper extends SQLiteOpenHelper {
return this;
}
public DatabaseHelper setAccess(long id, int uid, int block) {
synchronized (mContext.getApplicationContext()) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("block", block);
if (db.update("access", cv, "ID = ?", new String[]{Long.toString(id)}) != 1)
Log.e(TAG, "Set access failed");
for (AccessChangedListener listener : accessChangedListeners)
try {
listener.onChanged(uid);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
return this;
}
public DatabaseHelper clearAccess(int uid) {
synchronized (mContext.getApplicationContext()) {
SQLiteDatabase db = this.getReadableDatabase();
db.delete("access", "uid = ?", new String[]{Integer.toString(uid)});
}
for (AccessChangedListener listener : accessChangedListeners)
try {
listener.onChanged(uid);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
return this;
}
public Cursor getAccess(int uid) {
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT ID AS _id, * FROM access WHERE uid = ?";
String query = "SELECT ID AS _id, *";
query += " FROM access WHERE uid = ?";
query += " ORDER BY time DESC";
return db.rawQuery(query, new String[]{Integer.toString(uid)});
}
public Cursor getAccess() {
SQLiteDatabase db = this.getReadableDatabase();
return db.query("access", new String[]{"uid", "daddr", "dport", "block"}, "block >= 0", null, null, null, null);
}
public void addLogChangedListener(LogChangedListener listener) {
logChangedListeners.add(listener);
}

View file

@ -1,5 +1,24 @@
package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

View file

@ -1,5 +1,24 @@
package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;

View file

@ -22,10 +22,13 @@ package eu.faircode.netguard;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
@ -36,9 +39,11 @@ import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
@ -48,6 +53,7 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
@ -107,6 +113,7 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
public Button btnLaunch;
public ListView lvAccess;
public ImageButton btnClearAccess;
public TextView tvStatistics;
public ViewHolder(View itemView) {
@ -146,6 +153,7 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
btnLaunch = (Button) itemView.findViewById(R.id.btnLaunch);
lvAccess = (ListView) itemView.findViewById(R.id.lvAccess);
btnClearAccess = (ImageButton) itemView.findViewById(R.id.btnClearAccess);
tvStatistics = (TextView) itemView.findViewById(R.id.tvStatistics);
final View wifiParent = (View) cbWifi.getParent();
@ -417,11 +425,26 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
holder.btnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
holder.cbWifi.setChecked(rule.wifi_default);
holder.cbOther.setChecked(rule.other_default);
holder.cbScreenWifi.setChecked(rule.screen_wifi_default);
holder.cbScreenOther.setChecked(rule.screen_other_default);
holder.cbRoaming.setChecked(rule.roaming_default);
new AlertDialog.Builder(context)
.setTitle(R.string.msg_sure)
.setCancelable(true)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
holder.cbWifi.setChecked(rule.wifi_default);
holder.cbOther.setChecked(rule.other_default);
holder.cbScreenWifi.setChecked(rule.screen_wifi_default);
holder.cbScreenOther.setChecked(rule.screen_other_default);
holder.cbRoaming.setChecked(rule.roaming_default);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Do nothing
}
})
.create().show();
}
});
@ -447,10 +470,72 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> im
});
if (rule.expanded) {
AccessAdapter adapter = new AccessAdapter(context, dh.getAccess(rule.info.applicationInfo.uid));
holder.lvAccess.setAdapter(adapter);
} else
final AccessAdapter badapter = new AccessAdapter(context, dh.getAccess(rule.info.applicationInfo.uid));
holder.lvAccess.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, final int bposition, long bid) {
Cursor cursor = (Cursor) badapter.getItem(bposition);
final long id = cursor.getLong(cursor.getColumnIndex("ID"));
final String daddr = cursor.getString(cursor.getColumnIndex("daddr"));
final int dport = (cursor.isNull(cursor.getColumnIndex("dport")) ? -1 : cursor.getInt(cursor.getColumnIndex("dport")));
PopupMenu popup = new PopupMenu(context, view);
popup.inflate(R.menu.access);
popup.getMenu().findItem(R.id.menu_host).setTitle(daddr + (dport > 0 ? ":" + dport : ""));
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_allow:
dh.setAccess(id, rule.info.applicationInfo.uid, 0);
SinkholeService.reload(null, "allow host", context);
return true;
case R.id.menu_block:
dh.setAccess(id, rule.info.applicationInfo.uid, 1);
SinkholeService.reload(null, "block host", context);
return true;
case R.id.menu_clear:
dh.setAccess(id, rule.info.applicationInfo.uid, -1);
SinkholeService.reload(null, "clear host", context);
return true;
}
return false;
}
});
popup.show();
}
});
holder.lvAccess.setAdapter(badapter);
} else {
holder.lvAccess.setAdapter(null);
holder.lvAccess.setOnItemClickListener(null);
}
holder.btnClearAccess.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new AlertDialog.Builder(context)
.setTitle(R.string.msg_sure)
.setCancelable(true)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dh.clearAccess(rule.info.applicationInfo.uid);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Do nothing
}
})
.create().show();
}
});
// Traffic statistics
holder.tvStatistics.setText(context.getString(R.string.msg_mbday, rule.upspeed, rule.downspeed));

View file

@ -31,6 +31,7 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -74,6 +75,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@ -94,8 +96,9 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
private Object subscriptionsChangedListener = null;
private ParcelFileDescriptor vpn = null;
private HashMap<String, Boolean> mapDomainBlocked = new HashMap<>();
private HashMap<Integer, Boolean> mapUidAllowed = new HashMap<>();
private Map<String, Boolean> mapHostsBlocked = new HashMap<>();
private Map<Integer, Boolean> mapUidAllowed = new HashMap<>();
private Map<Integer, Map<String, Boolean>> mapUidIPFilters = new HashMap<>();
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
@ -649,7 +652,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
if (packet.uid > 0)
dh.updateAccess(packet, rr == null ? null : rr.QName);
else
else if (packet.dport != 53)
Log.w(TAG, "Unknown application packet=" + packet);
dh.close();
@ -795,8 +798,10 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
Log.i(TAG, "Start native log=" + log + " filter=" + filter);
// Prepare allowed/blocked lists
prepareAllowed(listAllowed);
// Prepare rules
prepareUidAllowed(listAllowed);
prepareHostsBlocked();
prepareUidIPFilters();
if (log || filter) {
int prio = Integer.parseInt(prefs.getString("loglevel", Integer.toString(Log.INFO)));
@ -809,15 +814,17 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
jni_stop(vpn.getFd(), clear);
}
private void prepareAllowed(List<Rule> listAllowed) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean use_hosts = prefs.getBoolean("use_hosts", false);
private void prepareUidAllowed(List<Rule> listAllowed) {
mapUidAllowed.clear();
for (Rule rule : listAllowed)
mapUidAllowed.put(rule.info.applicationInfo.uid, true);
}
mapDomainBlocked.clear();
private void prepareHostsBlocked() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean use_hosts = prefs.getBoolean("use_hosts", false);
mapHostsBlocked.clear();
if (use_hosts) {
File hosts = new File(getFilesDir(), "hosts.txt");
BufferedReader br = null;
@ -832,7 +839,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
if (line.length() > 0) {
String[] words = line.split("\\s+");
if (words.length == 2)
mapDomainBlocked.put(words[1], true);
mapHostsBlocked.put(words[1], true);
else
Log.i(TAG, "Invalid hosts file line: " + line);
}
@ -851,6 +858,42 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
}
}
private void prepareUidIPFilters() {
mapUidIPFilters.clear();
DatabaseHelper dh = null;
try {
dh = new DatabaseHelper(SinkholeService.this);
Cursor cursor = dh.getAccess();
int colUid = cursor.getColumnIndex("uid");
int colDAddr = cursor.getColumnIndex("daddr");
int colDPort = cursor.getColumnIndex("dport");
int colBlock = cursor.getColumnIndex("block");
while (cursor.moveToNext()) {
int uid = cursor.getInt(colUid);
String daddr = cursor.getString(colDAddr);
int dport = cursor.isNull(colDPort) ? -1 : cursor.getInt(colDPort);
boolean block = (cursor.getInt(colBlock) > 0);
if (!mapUidIPFilters.containsKey(uid))
mapUidIPFilters.put(uid, new HashMap<String, Boolean>());
try {
for (InetAddress iaddr : InetAddress.getAllByName(daddr)) {
String addr = iaddr.toString() + "/" + dport;
addr = addr.substring(addr.indexOf('/') + 1);
Log.i(TAG, "Set filter " + daddr + " " + addr + "=" + block);
mapUidIPFilters.get(uid).put(addr, block);
}
} catch (UnknownHostException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
cursor.close();
} finally {
dh.close();
}
}
private List<Rule> getAllowedRules(List<Rule> listRule) {
List<Rule> listAllowed = new ArrayList<>();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@ -979,7 +1022,7 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
// Called from native code
private boolean isDomainBlocked(String name) {
boolean blocked = (mapDomainBlocked.containsKey(name) && mapDomainBlocked.get(name));
boolean blocked = (mapHostsBlocked.containsKey(name) && mapHostsBlocked.get(name));
return blocked;
}
@ -987,16 +1030,27 @@ public class SinkholeService extends VpnService implements SharedPreferences.OnS
private boolean isAddressAllowed(Packet packet) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
packet.allowed = false;
if (packet.protocol == 6 /* TCP */ || packet.protocol == 17 /* UDP */) {
if (prefs.getBoolean("filter", false)) {
if (packet.uid <= 0) // unknown, root
packet.allowed = true;
else
packet.allowed = (mapUidAllowed.containsKey(packet.uid) && mapUidAllowed.get(packet.uid));
} else
packet.allowed = false;
} else
packet.allowed = false;
else {
boolean filtered = false;
if (mapUidIPFilters.containsKey(packet.uid)) {
String addr = packet.daddr + "/" + packet.dport;
if (mapUidIPFilters.get(packet.uid).containsKey(addr)) {
filtered = true;
packet.allowed = !mapUidIPFilters.get(packet.uid).get(addr);
Log.i(TAG, "Filtering " + addr + " allowed=" + packet.allowed);
}
}
if (!filtered)
packet.allowed = (mapUidAllowed.containsKey(packet.uid) && mapUidAllowed.get(packet.uid));
}
}
}
if (prefs.getBoolean("log", false))
logPacket(packet);

View file

@ -1220,6 +1220,8 @@ void handle_ip(const struct arguments *args, const uint8_t *pkt, const size_t le
else if (protocol == IPPROTO_TCP)
handle_tcp(args, pkt, length, payload, uid);
}
else
log_android(ANDROID_LOG_DEBUG, "Address %s/%u syn %d not allowed", dest, dport, syn);
#ifdef PROFILE_EVENTS
gettimeofday(&end, NULL);

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ip_allowed" android:state_checked="true" />
<item android:drawable="@drawable/ip_blocked" android:state_checked="false" />
</selector>

View file

@ -14,15 +14,11 @@
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textSize="12sp" />
<CheckBox
android:id="@+id/cbBlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:button="@drawable/ip"
android:scaleX="0.8"
android:scaleY="0.8" />
<ImageView
android:id="@+id/ivBlock"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical" />
<TextView
android:id="@+id/tvDest"

View file

@ -2,7 +2,6 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
android:paddingStart="@dimen/activity_horizontal_margin"

View file

@ -180,6 +180,13 @@
android:textStyle="italic"
android:visibility="gone" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_conditions"
android:textAppearance="@android:style/TextAppearance.Material.Title" />
<LinearLayout
android:id="@+id/llWifiAttr"
android:layout_width="wrap_content"
@ -279,6 +286,13 @@
android:text="@string/title_launch" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_access"
android:textAppearance="@android:style/TextAppearance.Material.Title" />
<eu.faircode.netguard.ExpandedListView
android:id="@+id/lvAccess"
android:layout_width="match_parent"
@ -286,9 +300,32 @@
android:layout_marginTop="4dp" />
<TextView
android:id="@+id/tvStatistics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp" />
android:layout_marginTop="4dp"
android:text="@string/title_precedence"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:textStyle="italic" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<ImageButton
android:id="@+id/btnClearAccess"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_delete_white_24dp" />
<TextView
android:id="@+id/tvStatistics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_host"
android:enabled="false"
android:title="" />
<item
android:id="@+id/menu_allow"
android:title="@string/title_allow" />
<item
android:id="@+id/menu_block"
android:title="@string/title_block" />
<item
android:id="@+id/menu_clear"
android:title="@string/menu_clear" />
</menu>

View file

@ -116,12 +116,15 @@ Since NetGuard has no internet permission, you know your internet traffic is not
<string name="msg_filter">Using filtering will cause Android to attribute data and power usage to NetGuard - Android assumes the data and power are being used by NetGuard, rather than the original applications</string>
<string name="msg_log_disabled">Traffic logging is disabled, use the switch above to enable logging. Traffic logging might result in extra battery usage.</string>
<string name="title_conditions">Conditions</string>
<string name="title_screen_wifi">Allow Wi-Fi when screen is on</string>
<string name="title_screen_other">Allow mobile when screen is on</string>
<string name="title_roaming">Block when roaming</string>
<string name="title_disabled">is disabled</string>
<string name="title_internet">has no internet permission</string>
<string name="title_launch">Start application</string>
<string name="title_access">Access</string>
<string name="title_precedence">Access rules take precedence over other rules</string>
<string name="title_rate">Rate</string>
<string name="title_allow">Allow</string>
<string name="title_block">Block</string>