[android] Refactor sending of zipped logs

- make bugreport and general feedback features send to flavor-specific support email
- improve freeing of resources in case of exceptions
- simplify and delete unused code

Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
This commit is contained in:
Konstantin Pastbin 2022-06-02 01:19:11 +03:00 committed by Viktor Govako
parent 48a8d2aba5
commit b07b3d3531
7 changed files with 78 additions and 101 deletions

View file

@ -30,7 +30,7 @@ public class FaqFragment extends BaseMwmFragment
private void reportBug()
{
Utils.sendBugReport(requireActivity(), "Organic Maps Bugreport");
Utils.sendBugReport(requireActivity(), "");
}
@Override

View file

@ -128,7 +128,7 @@ public class HelpFragment extends BaseMwmFragment implements View.OnClickListene
else if (id == R.id.faq)
((HelpActivity) getActivity()).stackFragment(FaqFragment.class, getString(R.string.faq), null);
else if (id == R.id.report)
Utils.sendFeedback(getActivity());
Utils.sendBugReport(getActivity(), "");
else if (id == R.id.support_us)
openLink(Constants.Url.SUPPORT_US);
else if (id == R.id.rate)

View file

@ -46,7 +46,6 @@ public final class Constants
public static class Email
{
public static final String FEEDBACK = "android@organicmaps.app";
public static final String SUPPORT = BuildConfig.SUPPORT_MAIL;
public static final String RATING = "rating@organicmaps.app";

View file

@ -98,14 +98,6 @@ public class StorageUtils
return externalDir + File.separator + LOGS_FOLDER;
}
@Nullable
static String getLogsZipPath(@NonNull Application application)
{
String zipFile = getExternalFilesDir(application) + File.separator + LOGS_FOLDER + ".zip";
File file = new File(zipFile);
return file.isFile() && file.exists() ? zipFile : null;
}
@NonNull
public static String getApkPath(@NonNull Application application)
{

View file

@ -343,16 +343,23 @@ public class Utils
}
}
// subject is optional (could be an empty string).
/**
* @param subject could be an empty string
*/
public static void sendBugReport(@NonNull Activity activity, @NonNull String subject)
{
subject = "Organic Maps Bugreport" + (TextUtils.isEmpty(subject) ? "" : ": " + subject);
LoggerFactory.INSTANCE.zipLogs(new SupportInfoWithLogsCallback(activity, subject,
Constants.Email.SUPPORT));
}
// TODO: Don't send logs with general feedback, send system information only (version, device name, connectivity, etc.)
public static void sendFeedback(@NonNull Activity activity)
{
LoggerFactory.INSTANCE.zipLogs(new SupportInfoWithLogsCallback(activity, "Organic Maps Feedback",
Constants.Email.FEEDBACK));
Constants.Email.SUPPORT));
}
public static void navigateToParent(@Nullable Activity activity)
@ -799,10 +806,11 @@ public class Utils
}
@Override
public void onCompleted(final boolean success)
public void onCompleted(final boolean success, @Nullable final String zipPath)
{
//TODO: delete zip file after its sent.
UiThread.run(() -> {
Activity activity = mActivityRef.get();
final Activity activity = mActivityRef.get();
if (activity == null)
return;
@ -810,21 +818,18 @@ public class Utils
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mEmail });
intent.putExtra(Intent.EXTRA_SUBJECT, "[" + BuildConfig.VERSION_NAME + "] " + mSubject);
//TODO: if zipping logs failed send a short text attachment with system info and logs zipping error/stacktrace.
if (success)
{
String logsZipFile = StorageUtils.getLogsZipPath(activity.getApplication());
if (!TextUtils.isEmpty(logsZipFile))
{
Uri uri = StorageUtils.getUriForFilePath(activity, logsZipFile);
intent.putExtra(Intent.EXTRA_STREAM, uri);
// Properly set permissions for intent, see
// https://developer.android.com/reference/androidx/core/content/FileProvider#include-the-permission-in-an-intent
intent.setData(uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
intent.setClipData(ClipData.newRawUri("", uri));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
final Uri uri = StorageUtils.getUriForFilePath(activity, zipPath);
intent.putExtra(Intent.EXTRA_STREAM, uri);
// Properly set permissions for intent, see
// https://developer.android.com/reference/androidx/core/content/FileProvider#include-the-permission-in-an-intent
intent.setData(uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
intent.setClipData(ClipData.newRawUri("", uri));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
// Do this so some email clients don't complain about empty body.
@ -836,7 +841,7 @@ public class Utils
}
catch (ActivityNotFoundException e)
{
CrashlyticsUtils.INSTANCE.logException(e);
LOGGER.w("No activities found which can handle sending a support message.", e);
}
});
}

View file

@ -35,14 +35,8 @@ public class LoggerFactory
public interface OnZipCompletedListener
{
/**
* Indicates about completion of zipping operation.
* <p>
* <b>NOTE:</b> called from the logger thread
* </p>
* @param success indicates about a status of zipping operation
*/
void onCompleted(boolean success);
// Called from the logger thread.
public void onCompleted(final boolean success, @Nullable final String zipPath);
}
public final static LoggerFactory INSTANCE = new LoggerFactory();
@ -92,7 +86,7 @@ public class LoggerFactory
.putBoolean(mApplication.getString(R.string.pref_enable_logging), enabled)
.apply();
updateLoggers();
getLogger(Type.MISC).i(TAG, "File logging " + (enabled ? " started to " + mLogsFolder : "stopped"));
getLogger(Type.MISC).i(TAG, "File logging " + (enabled ? "started to " + mLogsFolder : "stopped"));
}
/**
@ -137,7 +131,7 @@ public class LoggerFactory
if (TextUtils.isEmpty(mLogsFolder))
{
if (listener != null)
listener.onCompleted(false);
listener.onCompleted(false, null);
return;
}

View file

@ -1,14 +1,12 @@
package com.mapswithme.util.log;
import android.app.Application;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mapswithme.util.StorageUtils;
import com.mapswithme.util.Utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
@ -24,68 +22,50 @@ import java.util.zip.ZipOutputStream;
class ZipLogsTask implements Runnable
{
private final static String TAG = ZipLogsTask.class.getSimpleName();
private final static Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private final static int BUFFER_SIZE = 2048;
@NonNull
private final String mSourcePath;
private final String mLogsPath;
@NonNull
private final String mDestPath;
private final String mZipPath;
@Nullable
private final LoggerFactory.OnZipCompletedListener mOnCompletedListener;
@NonNull
private final Application mApplication;
ZipLogsTask(@NonNull Application application, @NonNull String sourcePath,
@NonNull String destPath,
ZipLogsTask(@NonNull Application application, @NonNull String logsPath,
@NonNull String zipPath,
@Nullable LoggerFactory.OnZipCompletedListener onCompletedListener)
{
mApplication = application;
mSourcePath = sourcePath;
mDestPath = destPath;
mLogsPath = logsPath;
mZipPath = zipPath;
mOnCompletedListener = onCompletedListener;
}
@Override
public void run()
{
saveSystemLogcat();
boolean success = zipFileAtPath(mSourcePath, mDestPath);
saveSystemLogcat(mLogsPath);
final boolean success = zipFileAtPath(mLogsPath, mZipPath);
if (mOnCompletedListener != null)
mOnCompletedListener.onCompleted(success);
mOnCompletedListener.onCompleted(success, mZipPath);
}
private boolean zipFileAtPath(@NonNull String sourcePath, @NonNull String toLocation)
{
File sourceFile = new File(sourcePath);
try(FileOutputStream dest = new FileOutputStream(toLocation, false);
if (!sourceFile.isDirectory())
return false;
try (
FileOutputStream dest = new FileOutputStream(toLocation, false);
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)))
{
if (sourceFile.isDirectory())
{
zipSubFolder(out, sourceFile, sourceFile.getParent().length());
}
else
{
try(FileInputStream fi = new FileInputStream(sourcePath);
BufferedInputStream origin = new BufferedInputStream(fi, BUFFER_SIZE))
{
ZipEntry entry = new ZipEntry(getLastPathComponent(sourcePath));
out.putNextEntry(entry);
byte[] data = new byte[BUFFER_SIZE];
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
out.write(data, 0, count);
}
} catch (Exception e)
{
Log.e(TAG, "Failed to read zip file entry '" + sourcePath +"' to location '"
+ toLocation + "'", e);
return false;
}
}
zipSubFolder(out, sourceFile, sourceFile.getParent().length());
}
catch (Exception e)
{
Log.e(TAG, "Failed to zip file '" + sourcePath +"' to location '" + toLocation + "'", e);
Log.e(TAG, "Failed to zip file '" + sourcePath + "' to location '" + toLocation + "'", e);
return false;
}
return true;
@ -95,7 +75,9 @@ class ZipLogsTask implements Runnable
int basePathLength) throws IOException
{
File[] fileList = folder.listFiles();
BufferedInputStream origin;
if (fileList == null)
throw new IOException("Can't get files list of " + folder.getPath());
for (File file : fileList)
{
if (file.isDirectory())
@ -106,18 +88,19 @@ class ZipLogsTask implements Runnable
{
byte[] data = new byte[BUFFER_SIZE];
String unmodifiedFilePath = file.getPath();
String relativePath = unmodifiedFilePath
.substring(basePathLength);
FileInputStream fi = new FileInputStream(unmodifiedFilePath);
origin = new BufferedInputStream(fi, BUFFER_SIZE);
ZipEntry entry = new ZipEntry(relativePath);
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1)
String relativePath = unmodifiedFilePath.substring(basePathLength);
try (
FileInputStream fi = new FileInputStream(unmodifiedFilePath);
BufferedInputStream origin = new BufferedInputStream(fi, BUFFER_SIZE))
{
out.write(data, 0, count);
ZipEntry entry = new ZipEntry(relativePath);
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1)
{
out.write(data, 0, count);
}
}
origin.close();
}
}
}
@ -130,19 +113,27 @@ class ZipLogsTask implements Runnable
return segments[segments.length - 1];
}
private void saveSystemLogcat()
private void saveSystemLogcat(String path)
{
String fullName = StorageUtils.getLogsFolder(mApplication) + File.separator + "logcat.log";
final File file = new File(fullName);
InputStreamReader reader = null;
FileWriter writer = null;
final String cmd = "logcat -d -v time";
Process process;
try
{
writer = new FileWriter(file);
process = Runtime.getRuntime().exec(cmd);
}
catch (IOException e)
{
LOGGER.e(TAG, "Failed to get system logcat", e);
return;
}
path += File.separator + "logcat.log";
final File file = new File(path);
try (
InputStreamReader reader = new InputStreamReader(process.getInputStream());
FileWriter writer = new FileWriter(file))
{
FileLoggerStrategy.WriteTask.writeSystemInformation(mApplication, writer);
String cmd = "logcat -d -v time";
Process process = Runtime.getRuntime().exec(cmd);
reader = new InputStreamReader(process.getInputStream());
char[] buffer = new char[10000];
do
{
@ -154,12 +145,8 @@ class ZipLogsTask implements Runnable
}
catch (Throwable e)
{
Log.e(TAG, "Failed to save system logcat to file: " + file, e);
}
finally
{
Utils.closeSafely(writer);
Utils.closeSafely(reader);
LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
LOGGER.e(TAG, "Failed to save system logcat to " + path, e);
}
}
}