mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-07-03 21:21:12 +08:00
Show logs inside the app
The app now shows its own logs along with the root and the mitm addon ones. Closes #282
This commit is contained in:
parent
cbdc4967ca
commit
77560bdebd
@ -24,18 +24,20 @@ import androidx.annotation.Nullable;
|
||||
|
||||
public class Log {
|
||||
public static final int LOG_LEVEL_INFO = 4;
|
||||
public static final String DEFAULT_LOGGER_PATH = "pcapdroid.log";
|
||||
public static final String ROOT_LOGGER_PATH = "pcapd.log";
|
||||
public static final String MITM_LOGGER_PATH = "mitmaddon.log";
|
||||
public static int DEFAULT_LOGGER;
|
||||
public static int MITMADDON_LOGGER;
|
||||
|
||||
public static void init(String basedir) {
|
||||
DEFAULT_LOGGER = CaptureService.initLogger(basedir + "/pcapdroid.log", LOG_LEVEL_INFO);
|
||||
MITMADDON_LOGGER = CaptureService.initLogger(basedir + "/mitmaddon.log", LOG_LEVEL_INFO);
|
||||
android.util.Log.e("tag", basedir);
|
||||
public static void init(String cachedir) {
|
||||
DEFAULT_LOGGER = CaptureService.initLogger(cachedir + "/" + DEFAULT_LOGGER_PATH, LOG_LEVEL_INFO);
|
||||
MITMADDON_LOGGER = CaptureService.initLogger(cachedir + "/" + MITM_LOGGER_PATH, LOG_LEVEL_INFO);
|
||||
}
|
||||
|
||||
public static void writeLog(int logger, int level, @Nullable String tag, @NonNull String message) {
|
||||
if(!PCAPdroid.isUnderTest())
|
||||
CaptureService.writeLog(logger, level, "[" + tag + "] " + message);
|
||||
CaptureService.writeLog(logger, level, ((tag != null) ? ("[" + tag + "] ") : "") + message);
|
||||
}
|
||||
|
||||
public static void d(@Nullable String tag, @NonNull String message) {
|
||||
@ -47,18 +49,44 @@ public class Log {
|
||||
writeLog(DEFAULT_LOGGER, android.util.Log.INFO, tag, message);
|
||||
}
|
||||
|
||||
public static void i(int logger, @NonNull String message) {
|
||||
writeLog(logger, android.util.Log.INFO, null, message);
|
||||
}
|
||||
|
||||
public static void w(@Nullable String tag, @NonNull String message) {
|
||||
android.util.Log.w(tag, message);
|
||||
writeLog(DEFAULT_LOGGER, android.util.Log.WARN, tag, message);
|
||||
}
|
||||
|
||||
public static void w(int logger, @NonNull String message) {
|
||||
writeLog(logger, android.util.Log.WARN, null, message);
|
||||
}
|
||||
|
||||
public static void e(@Nullable String tag, @NonNull String message) {
|
||||
android.util.Log.e(tag, message);
|
||||
writeLog(DEFAULT_LOGGER, android.util.Log.ERROR, tag, message);
|
||||
}
|
||||
|
||||
public static void e(int logger, @NonNull String message) {
|
||||
writeLog(logger, android.util.Log.ERROR, null, message);
|
||||
}
|
||||
|
||||
public static void wtf(@Nullable String tag, @NonNull String message) {
|
||||
android.util.Log.wtf(tag, message);
|
||||
writeLog(DEFAULT_LOGGER, android.util.Log.ASSERT, tag, message); // ANDROID_LOG_FATAL
|
||||
}
|
||||
|
||||
public static void level(int logger, int level, @NonNull String message) {
|
||||
switch (level) {
|
||||
case android.util.Log.INFO:
|
||||
Log.i(logger, message);
|
||||
break;
|
||||
case android.util.Log.WARN:
|
||||
Log.w(logger, message);
|
||||
break;
|
||||
case android.util.Log.ERROR:
|
||||
Log.e(logger, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,8 +48,8 @@ import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MitmAddon {
|
||||
public static final long PACKAGE_VERSION_CODE = 9;
|
||||
public static final String PACKAGE_VERSION_NAME = "v0.9";
|
||||
public static final long PACKAGE_VERSION_CODE = 10;
|
||||
public static final String PACKAGE_VERSION_NAME = "v0.10";
|
||||
public static final String REPOSITORY = "https://github.com/emanuele-f/PCAPdroid-mitm";
|
||||
private static final String TAG = "MitmAddon";
|
||||
private final Context mContext;
|
||||
|
||||
@ -91,6 +91,7 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener
|
||||
WEBSOCKET_CLIENT_MSG,
|
||||
WEBSOCKET_SERVER_MSG,
|
||||
MASTER_SECRET,
|
||||
LOG,
|
||||
}
|
||||
|
||||
private static class PendingMessage {
|
||||
@ -234,7 +235,9 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener
|
||||
|
||||
if(type == MsgType.MASTER_SECRET)
|
||||
logMasterSecret(msg);
|
||||
else if(type == MsgType.RUNNING) {
|
||||
else if(type == MsgType.LOG) {
|
||||
handleLog(msg);
|
||||
} else if(type == MsgType.RUNNING) {
|
||||
Log.i(TAG, "MITM proxy is running");
|
||||
proxyRunning.postValue(true);
|
||||
} else {
|
||||
@ -347,6 +350,8 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener
|
||||
return MsgType.WEBSOCKET_SERVER_MSG;
|
||||
case "secret":
|
||||
return MsgType.MASTER_SECRET;
|
||||
case "log":
|
||||
return MsgType.LOG;
|
||||
default:
|
||||
return MsgType.UNKNOWN;
|
||||
}
|
||||
@ -362,6 +367,19 @@ public class MitmReceiver implements Runnable, ConnectionsListener, MitmListener
|
||||
mKeylog.write(0xa);
|
||||
}
|
||||
|
||||
private void handleLog(byte[] message) {
|
||||
try {
|
||||
String msg = new String(message, StandardCharsets.US_ASCII);
|
||||
|
||||
// format: 1:message
|
||||
if (msg.length() < 3)
|
||||
return;
|
||||
|
||||
int lvl = Integer.parseInt(msg.substring(0, 1));
|
||||
Log.level(Log.MITMADDON_LOGGER, lvl, msg.substring(2));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
public boolean isProxyRunning() {
|
||||
return Boolean.TRUE.equals(proxyRunning.getValue());
|
||||
}
|
||||
|
||||
@ -41,6 +41,10 @@ class BaseActivity extends AppCompatActivity {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Fragment getFragmentAtPos(int pos) {
|
||||
return getSupportFragmentManager().findFragmentByTag("f" + pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(mBackAction && (item.getItemId() == android.R.id.home)) {
|
||||
|
||||
@ -73,7 +73,7 @@ public class ConnectionDetailsActivity extends BaseActivity implements Connectio
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.connection_details);
|
||||
displayBackAction();
|
||||
setContentView(R.layout.activity_connection_details);
|
||||
setContentView(R.layout.tabs_activity_fixed);
|
||||
|
||||
int incr_id = getIntent().getIntExtra(CONN_ID_KEY, -1);
|
||||
if(incr_id != -1) {
|
||||
|
||||
@ -23,70 +23,112 @@ import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.MenuProvider;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.emanuelef.remote_capture.Log;
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import com.emanuelef.remote_capture.fragments.LogviewFragment;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
public class LogviewActivity extends BaseActivity implements MenuProvider {
|
||||
private static final String TAG = "LogviewActivity";
|
||||
private String mLogText;
|
||||
private ViewPager2 mPager;
|
||||
private StateAdapter mPagerAdapter;
|
||||
|
||||
private static final int POS_APP_LOG = 0;
|
||||
private static final int POS_ROOT_LOG = 1;
|
||||
private static final int POS_MITM_LOG = 2;
|
||||
private static final int NUM_POS = 3;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.root_log);
|
||||
setContentView(R.layout.logview_activity);
|
||||
setTitle(R.string.app_log);
|
||||
setContentView(R.layout.tabs_activity_fixed);
|
||||
addMenuProvider(this);
|
||||
|
||||
TextView logView = findViewById(R.id.log);
|
||||
mLogText = readLog();
|
||||
|
||||
logView.setText(!mLogText.isEmpty() ? mLogText : getString(R.string.error));
|
||||
mPager = findViewById(R.id.pager);
|
||||
setupTabs();
|
||||
}
|
||||
|
||||
private String readLog() {
|
||||
try {
|
||||
String logpath = getCacheDir().getPath() + "/pcapd.log";
|
||||
BufferedReader reader = new BufferedReader(new FileReader(logpath));
|
||||
private void setupTabs() {
|
||||
mPagerAdapter = new StateAdapter(this);
|
||||
mPager.setAdapter(mPagerAdapter);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
new TabLayoutMediator(findViewById(R.id.tablayout), mPager, (tab, position) ->
|
||||
tab.setText(getString(mPagerAdapter.getPageTitle(position)))
|
||||
).attach();
|
||||
}
|
||||
|
||||
while((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
builder.append("\n");
|
||||
}
|
||||
private static class StateAdapter extends FragmentStateAdapter {
|
||||
final String mCacheDir;
|
||||
|
||||
return builder.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
StateAdapter(final FragmentActivity fa) {
|
||||
super(fa);
|
||||
mCacheDir = fa.getCacheDir().getAbsolutePath();
|
||||
}
|
||||
|
||||
return "";
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int pos) {
|
||||
switch (pos) {
|
||||
case POS_APP_LOG:
|
||||
return LogviewFragment.newInstance(mCacheDir + "/" + Log.DEFAULT_LOGGER_PATH);
|
||||
case POS_ROOT_LOG:
|
||||
return LogviewFragment.newInstance(mCacheDir + "/" + Log.ROOT_LOGGER_PATH);
|
||||
case POS_MITM_LOG:
|
||||
default:
|
||||
return LogviewFragment.newInstance(mCacheDir + "/" + Log.MITM_LOGGER_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return NUM_POS;
|
||||
}
|
||||
|
||||
public int getPageTitle(final int pos) {
|
||||
switch (pos) {
|
||||
case POS_APP_LOG:
|
||||
return R.string.app;
|
||||
case POS_ROOT_LOG:
|
||||
return R.string.root;
|
||||
case POS_MITM_LOG:
|
||||
default:
|
||||
return R.string.mitm_addon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateMenu(@NonNull Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.copy_share_menu, menu);
|
||||
inflater.inflate(R.menu.log_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
LogviewFragment fragment = (LogviewFragment) getFragmentAtPos(mPager.getCurrentItem());
|
||||
if(fragment == null)
|
||||
return false;
|
||||
|
||||
if(id == R.id.copy_to_clipboard) {
|
||||
Utils.copyToClipboard(this, mLogText);
|
||||
String logText = fragment.getLog();
|
||||
|
||||
if(id == R.id.reload) {
|
||||
fragment.reloadLog();
|
||||
return true;
|
||||
} else if(id == R.id.copy_to_clipboard) {
|
||||
Utils.copyToClipboard(this, logText);
|
||||
return true;
|
||||
} else if(id == R.id.share) {
|
||||
Utils.shareText(this, getString(R.string.root_log), mLogText);
|
||||
Utils.shareText(this, getString(R.string.app_log), logText);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -211,8 +211,6 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
Menu navMenu = mNavView.getMenu();
|
||||
navMenu.findItem(R.id.open_root_log).setVisible(Prefs.isRootCaptureEnabled(mPrefs));
|
||||
checkPaidDrawerEntries();
|
||||
}
|
||||
|
||||
@ -483,7 +481,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
|
||||
} else if(id == R.id.firewall) {
|
||||
Intent intent = new Intent(MainActivity.this, FirewallActivity.class);
|
||||
startActivity(intent);
|
||||
} else if(id == R.id.open_root_log) {
|
||||
} else if(id == R.id.open_log) {
|
||||
Intent intent = new Intent(MainActivity.this, LogviewActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (id == R.id.action_donate) {
|
||||
|
||||
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.fragment.app.Fragment;
|
||||
|
||||
import com.emanuelef.remote_capture.R;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
public class LogviewFragment extends Fragment {
|
||||
private static final String TAG = "LogviewFragment";
|
||||
private String mLogPath;
|
||||
private String mLogText;
|
||||
private TextView mLogView;
|
||||
|
||||
public static LogviewFragment newInstance(String path) {
|
||||
LogviewFragment fragment = new LogviewFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable("path", path);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.logview_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
Bundle args = getArguments();
|
||||
assert args != null;
|
||||
mLogPath = args.getString("path");
|
||||
assert(mLogPath != null);
|
||||
|
||||
mLogView = view.findViewById(R.id.log);
|
||||
reloadLog();
|
||||
}
|
||||
|
||||
public void reloadLog() {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(mLogPath));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String line;
|
||||
|
||||
while((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
mLogText = builder.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
mLogText = "";
|
||||
}
|
||||
|
||||
mLogView.setText(!mLogText.isEmpty() ? mLogText : getString(R.string.no_data));
|
||||
}
|
||||
|
||||
public String getLog() {
|
||||
return mLogText;
|
||||
}
|
||||
}
|
||||
@ -11,12 +11,16 @@ static int num_loggers = 0;
|
||||
|
||||
struct log_writer {
|
||||
FILE *f;
|
||||
char *path;
|
||||
int id;
|
||||
int level;
|
||||
bool errored;
|
||||
};
|
||||
|
||||
static void pd_destroy_logger(struct log_writer *logger) {
|
||||
fclose(logger->f);
|
||||
if(logger->f)
|
||||
fclose(logger->f);
|
||||
pd_free(logger->path);
|
||||
pd_free(logger);
|
||||
}
|
||||
|
||||
@ -33,26 +37,17 @@ void pd_close_loggers() {
|
||||
}
|
||||
|
||||
int pd_init_logger(const char *path, int min_lvl) {
|
||||
FILE *f = fopen(path, "w");
|
||||
int rv;
|
||||
|
||||
if(!f) {
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ANDROID_LOG_ERROR, logtag,
|
||||
"pd_init_logger %s failed[%d]: %s", path, errno, strerror(errno));
|
||||
#endif
|
||||
return -errno;
|
||||
}
|
||||
|
||||
struct log_writer *logger = (struct log_writer*) pd_calloc(1, sizeof(struct log_writer));
|
||||
if(!logger) {
|
||||
rv = -errno;
|
||||
fclose(f);
|
||||
return rv;
|
||||
}
|
||||
if(!logger)
|
||||
return -errno;
|
||||
|
||||
logger->level = min_lvl;
|
||||
logger->f = f;
|
||||
logger->path = pd_strdup(path);
|
||||
if(!logger->path) {
|
||||
pd_free(logger);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
loggers = pd_realloc(loggers, sizeof(void*) * (num_loggers + 1));
|
||||
@ -60,8 +55,8 @@ int pd_init_logger(const char *path, int min_lvl) {
|
||||
pd_destroy_logger(logger);
|
||||
rv = -1;
|
||||
} else {
|
||||
loggers[num_loggers++] = logger;
|
||||
logger->id = rv = num_loggers;
|
||||
loggers[num_loggers] = logger;
|
||||
logger->id = rv = num_loggers++;
|
||||
}
|
||||
pthread_mutex_unlock(&mutex);
|
||||
|
||||
@ -73,7 +68,6 @@ int pd_log_write(int logger_id, int lvl, const char *msg) {
|
||||
char dtbuf[64];
|
||||
struct tm tm;
|
||||
time_t tnow = time(NULL);
|
||||
|
||||
strftime(dtbuf, sizeof(dtbuf), "%d/%b/%Y %H:%M:%S", localtime_r(&tnow, &tm));
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
@ -88,7 +82,22 @@ int pd_log_write(int logger_id, int lvl, const char *msg) {
|
||||
if(logger->level > lvl)
|
||||
goto unlock;
|
||||
|
||||
if(ferror(logger->f)) {
|
||||
if(!logger->f && !logger->errored) {
|
||||
// only overwrite the file when writing to it
|
||||
logger->f = fopen(logger->path, "w");
|
||||
|
||||
if(!logger->f) {
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ANDROID_LOG_ERROR, logtag,
|
||||
"pd_init_logger %s failed[%d]: %s", logger->path, errno, strerror(errno));
|
||||
#endif
|
||||
rv = -errno;
|
||||
logger->errored = true;
|
||||
goto unlock;
|
||||
}
|
||||
}
|
||||
|
||||
if(!logger->f || ferror(logger->f)) {
|
||||
rv = -EINVAL;
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@ -10,5 +9,8 @@
|
||||
android:id="@+id/log"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:textSize="13sp"
|
||||
tools:text="[i] log..."
|
||||
android:textIsSelectable="true" />
|
||||
</ScrollView>
|
||||
26
app/src/main/res/menu/log_menu.xml
Normal file
26
app/src/main/res/menu/log_menu.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/reload"
|
||||
android:title="@string/update_now"
|
||||
android:orderInCategory="5"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/copy_to_clipboard"
|
||||
android:title="@string/copy_to_clipboard"
|
||||
android:orderInCategory="10"
|
||||
android:icon="@drawable/ic_content_copy"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/share"
|
||||
android:title="@string/share"
|
||||
android:orderInCategory="20"
|
||||
android:icon="@drawable/ic_share"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@ -18,10 +18,9 @@
|
||||
android:title="@string/malware_detection"
|
||||
android:icon="@drawable/ic_bug" />
|
||||
<item
|
||||
android:id="@+id/open_root_log"
|
||||
android:title="@string/root_log"
|
||||
android:icon="@drawable/ic_text_snippet"
|
||||
android:visible="false" />
|
||||
android:id="@+id/open_log"
|
||||
android:title="@string/app_log"
|
||||
android:icon="@drawable/ic_text_snippet" />
|
||||
<item
|
||||
android:id="@+id/paid_features"
|
||||
android:visible="false"
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<string name="whois_lookup" translatable="false">WHOIS</string>
|
||||
<string name="installation_id" translatable="false">Installation ID</string>
|
||||
<string name="license_code" translatable="false">License code</string>
|
||||
<string name="root" translatable="false">root</string>
|
||||
<string name="app_license" translatable="false">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 <a href='https://www.gnu.org/licenses/gpl-3.0-standalone.html'>GNU General Public License or later</a> for
|
||||
@ -126,7 +127,8 @@
|
||||
<string name="root_capture">Capture as root</string>
|
||||
<string name="root_capture_summary">Allows PCAPdroid to run with other VPN apps</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="root_log">Root log</string>
|
||||
<string name="app_log">Log</string>
|
||||
<string name="no_data">No data</string>
|
||||
<string name="search">Search…</string>
|
||||
<string name="app_val">App: %1$s</string>
|
||||
<string name="ip_address_val">IP address: %1$s</string>
|
||||
@ -278,6 +280,7 @@
|
||||
<string name="cert_reinstall_required">The CA certificate is not installed, run the mitm setup wizard</string>
|
||||
<string name="mitm_addon_bad_version">Bad PCAPdroid mitm addon version. Uninstall it and retry</string>
|
||||
<string name="mitm_addon_new_version">The PCAPdroid mitm addon must be upgraded</string>
|
||||
<string name="mitm_addon">mitm addon</string>
|
||||
<string name="upgrade_action">Upgrade</string>
|
||||
<string name="export_failed">Export failed</string>
|
||||
<string name="not_encrypted">Not encrypted</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user