aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android/preference/LogExporterPreference.java
blob: a96cb9c8eb7d18e47fb74226efed32414ad57284 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/*
 * Copyright © 2018 Samuel Holland <samuel@sholland.org>
 * Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.wireguard.android.preference;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.preference.Preference;
import android.util.AttributeSet;
import android.util.Log;

import com.wireguard.android.Application;
import com.wireguard.android.R;
import com.wireguard.android.util.ExceptionLoggers;
import com.wireguard.android.util.FragmentUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * Preference implementing a button that asynchronously exports logs.
 */

public class LogExporterPreference extends Preference {
    private static final String TAG = "WireGuard/" + LogExporterPreference.class.getSimpleName();

    @Nullable private String exportedFilePath;

    public LogExporterPreference(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    private void exportLog() {
        Application.getAsyncWorker().supplyAsync(() -> {
            final File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            final File file = new File(path, "wireguard-log.txt");
            if (!path.isDirectory() && !path.mkdirs())
                throw new IOException("Cannot create output directory");

            /* We would like to simply run `builder.redirectOutput(file);`, but this is API 26.
             * Instead we have to do this dance, since logcat appends.
             */
            new FileOutputStream(file).close();

            try {
                final Process process = Runtime.getRuntime().exec(new String[]{
                        "logcat", "-b", "all", "-d", "-v", "threadtime", "-f", file.getAbsolutePath(), "*:V"});
                if (process.waitFor() != 0) {
                    try (final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                        final StringBuilder errors = new StringBuilder();
                        errors.append("Unable to run logcat: ");
                        String line;
                        while ((line = reader.readLine()) != null)
                            errors.append(line);
                        throw new Exception(errors.toString());
                    }
                }
            } catch (final Exception e) {
                // noinspection ResultOfMethodCallIgnored
                file.delete();
                throw e;
            }
            return file.getAbsolutePath();
        }).whenComplete(this::exportLogComplete);
    }

    private void exportLogComplete(final String filePath, @Nullable final Throwable throwable) {
        if (throwable != null) {
            final String error = ExceptionLoggers.unwrapMessage(throwable);
            final String message = getContext().getString(R.string.log_export_error, error);
            Log.e(TAG, message, throwable);
            Snackbar.make(
                    FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
                    message, Snackbar.LENGTH_LONG).show();
            setEnabled(true);
        } else {
            exportedFilePath = filePath;
            notifyChanged();
        }
    }

    @Override
    public CharSequence getSummary() {
        return exportedFilePath == null ?
                getContext().getString(R.string.log_export_summary) :
                getContext().getString(R.string.log_export_success, exportedFilePath);
    }

    @Override
    public CharSequence getTitle() {
        return getContext().getString(R.string.log_exporter_title);
    }

    @Override
    protected void onClick() {
        FragmentUtils.getPrefActivity(this).ensurePermissions(
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                (permissions, granted) -> {
                    if (granted.length > 0 && granted[0] == PackageManager.PERMISSION_GRANTED) {
                        setEnabled(false);
                        exportLog();
                    }
                });
    }

}