From 17068aa00d3f8eac7bfcfc4e7c70fb5742a9b4fc Mon Sep 17 00:00:00 2001 From: Sergey Eremin Date: Sat, 22 Jul 2017 04:45:13 +0300 Subject: [PATCH] Issue #241 Bug reporting reworked --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 5 +- .../yeriomin/yalpstore/AboutActivity.java | 4 +- .../yeriomin/yalpstore/BitmapManager.java | 10 +- .../yeriomin/yalpstore/BugReportActivity.java | 69 ++++++++++++ .../yalpstore/CrashLetterActivity.java | 82 -------------- .../yalpstore/CrashLetterBuilder.java | 33 ------ .../CrashLetterStackTraceBuilder.java | 35 ------ .../yalpstore/DebugHttpClientAdapter.java | 5 +- .../yalpstore/DeltaPatcherFactory.java | 6 +- .../yalpstore/DeltaPatcherGDiffGzipped.java | 11 +- .../yeriomin/yalpstore/FileProvider.java | 3 - .../HttpURLConnectionDownloadTask.java | 9 +- .../yalpstore/InstalledApkCopier.java | 14 +-- .../yalpstore/NativeHttpClientAdapter.java | 3 +- .../yeriomin/yalpstore/UpdatableAppsTask.java | 6 +- .../com/github/yeriomin/yalpstore/Util.java | 13 +++ .../yeriomin/yalpstore/YalpStoreActivity.java | 3 + .../YalpStoreUncaughtExceptionHandler.java | 13 +-- .../yalpstore/bugreport/BugReportBuilder.java | 62 +++++++++++ .../BugReportDeviceInfoBuilder.java} | 28 ++--- .../BugReportLogBuilder.java} | 26 ++--- .../bugreport/BugReportMessageBuilder.java | 33 ++++++ .../BugReportPreferencesBuilder.java | 55 +++++++++ .../yalpstore/bugreport/BugReportSender.java | 50 +++++++++ .../bugreport/BugReportSenderEmail.java | 62 +++++++++++ .../bugreport/BugReportSenderFtp.java | 105 ++++++++++++++++++ .../yalpstore/bugreport/BugReportService.java | 41 +++++++ .../main/res/drawable-hdpi/ic_bug_report.png | Bin 0 -> 266 bytes .../main/res/drawable-xhdpi/ic_bug_report.png | Bin 0 -> 351 bytes .../res/drawable-xxhdpi/ic_bug_report.png | Bin 0 -> 469 bytes .../res/drawable-xxxhdpi/ic_bug_report.png | Bin 0 -> 519 bytes app/src/main/res/drawable/ic_bug_report.png | Bin 0 -> 231 bytes .../res/layout/bugreport_activity_layout.xml | 72 ++++++++++++ app/src/main/res/menu/menu_main.xml | 4 + app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/xml/paths.xml | 1 - drawable-source/ic_bug_report.svg | 54 +++++++++ 39 files changed, 696 insertions(+), 229 deletions(-) create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/BugReportActivity.java delete mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterActivity.java delete mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterBuilder.java delete mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterStackTraceBuilder.java create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportBuilder.java rename app/src/main/java/com/github/yeriomin/yalpstore/{CrashLetterDeviceInfoBuilder.java => bugreport/BugReportDeviceInfoBuilder.java} (90%) rename app/src/main/java/com/github/yeriomin/yalpstore/{CrashLetterLogBuilder.java => bugreport/BugReportLogBuilder.java} (58%) create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportMessageBuilder.java create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportPreferencesBuilder.java create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSender.java create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderEmail.java create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderFtp.java create mode 100644 app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportService.java create mode 100644 app/src/main/res/drawable-hdpi/ic_bug_report.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_bug_report.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_bug_report.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_bug_report.png create mode 100644 app/src/main/res/drawable/ic_bug_report.png create mode 100644 app/src/main/res/layout/bugreport_activity_layout.xml create mode 100644 drawable-source/ic_bug_report.svg diff --git a/app/build.gradle b/app/build.gradle index b4f4bb243..c690f7acd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,6 +81,7 @@ android { } dependencies { + compile 'commons-net:commons-net:3.5' compile 'com.nothome:javaxdelta:2.0.1' debugCompile 'com.github.yeriomin:play-store-api:master-SNAPSHOT' releaseCompile 'com.github.yeriomin:play-store-api:0.' + android.defaultConfig.versionCode diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a8378d84e..6b8ac00ae 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ - + + diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java b/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java index cc94e63f6..11de95f4f 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/AboutActivity.java @@ -14,6 +14,8 @@ import android.view.View; import android.widget.TextView; import android.widget.Toast; +import com.github.yeriomin.yalpstore.bugreport.BugReportSenderEmail; + public class AboutActivity extends YalpStoreActivity { @Override @@ -30,7 +32,7 @@ public class AboutActivity extends YalpStoreActivity { @Override public void onClick(View v) { super.onClick(v); - CrashLetterActivity.send(AboutActivity.this, null); + new BugReportSenderEmail(getApplicationContext()).send(); } }); findViewById(R.id.website).setOnClickListener(new UriOpeningListener()); diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/BitmapManager.java b/app/src/main/java/com/github/yeriomin/yalpstore/BitmapManager.java index d2417787d..213c28168 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/BitmapManager.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/BitmapManager.java @@ -96,20 +96,22 @@ public class BitmapManager { options.inJustDecodeBounds = false; options.inDither = false; return BitmapFactory.decodeStream(new FileInputStream(cached), null, options); - } catch (Throwable e) { + } catch (IOException e) { Log.e(BitmapManager.class.getName(), "Could not get cached bitmap: " + e.getClass().getName() + " " + e.getMessage()); return null; } } static private void cacheBitmapOnDisk(Bitmap bitmap, File cached) { + FileOutputStream out = null; try { - FileOutputStream out = new FileOutputStream(cached); + out = new FileOutputStream(cached); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); - out.close(); - } catch (Exception e) { + } catch (IOException e) { e.printStackTrace(); + } finally { + Util.closeSilently(out); } } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/BugReportActivity.java b/app/src/main/java/com/github/yeriomin/yalpstore/BugReportActivity.java new file mode 100644 index 000000000..0a127b65b --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/BugReportActivity.java @@ -0,0 +1,69 @@ +package com.github.yeriomin.yalpstore; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; + +import com.github.yeriomin.yalpstore.bugreport.BugReportService; + +public class BugReportActivity extends Activity { + + private String stackTrace; + private boolean triggeredByCrash; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.bugreport_activity_layout); + onNewIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + stackTrace = intent.getStringExtra(BugReportService.INTENT_STACKTRACE); + triggeredByCrash = !TextUtils.isEmpty(stackTrace); + setTitle(triggeredByCrash ? R.string.dialog_title_application_crashed : R.string.action_bug_report); + ((TextView) findViewById(R.id.explanation)).setText(triggeredByCrash ? R.string.bug_report_explanation_crash : R.string.bug_report_explanation_bug_report); + if (!PreferenceActivity.getBoolean(this, PreferenceActivity.PREFERENCE_APP_PROVIDED_EMAIL)) { + ((EditText) findViewById(R.id.identification)).setText(PreferenceActivity.getString(this, PreferenceActivity.PREFERENCE_EMAIL)); + } + } + + public void sendBugReport(View view) { + startService(getBugReportIntent( + stackTrace, + ((EditText) findViewById(R.id.message)).getText().toString(), + ((EditText) findViewById(R.id.identification)).getText().toString() + )); + finishAndGoToHomeScreen(view); + } + + public void finishAndGoToHomeScreen(View view) { + finish(); + if (triggeredByCrash) { + goToHomeScreen(); + } + } + + private void goToHomeScreen() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + private Intent getBugReportIntent(String stackTrace, String message, String identification) { + Intent intentBugReport = new Intent(getApplicationContext(), BugReportService.class); + intentBugReport.setAction(BugReportService.ACTION_SEND_FTP); + intentBugReport.putExtra(BugReportService.INTENT_STACKTRACE, stackTrace); + intentBugReport.putExtra(BugReportService.INTENT_MESSAGE, message); + intentBugReport.putExtra(BugReportService.INTENT_IDENTIFICATION, identification); + return intentBugReport; + } +} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterActivity.java b/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterActivity.java deleted file mode 100644 index 50dc15390..000000000 --- a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.github.yeriomin.yalpstore; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.text.TextUtils; - -import java.util.ArrayList; - -public class CrashLetterActivity extends Activity { - - static public boolean send(Context context, String stackTrace) { - Intent emailIntent = getEmailIntent(context, stackTrace); - if (emailIntent.resolveActivity(context.getPackageManager()) != null) { - context.startActivity(emailIntent); - return true; - } - return false; - } - - static private Intent getEmailIntent(Context context, String stackTrace) { - Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); - String developerEmail = context.getString(R.string.about_developer_email); - emailIntent.setData(Uri.fromParts("mailto", developerEmail, null)); - emailIntent.setType("plain/text"); - emailIntent.setType("message/rfc822"); - emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {developerEmail}); - emailIntent.putExtra( - Intent.EXTRA_SUBJECT, - context.getString( - TextUtils.isEmpty(stackTrace) ? R.string.email_subject_feedback : R.string.email_subject_crash_report, - BuildConfig.APPLICATION_ID, - BuildConfig.VERSION_NAME - ) - ); - ArrayList uris = new ArrayList<>(); - uris.add(new CrashLetterDeviceInfoBuilder(context).getUri()); - uris.add(new CrashLetterLogBuilder(context).getUri()); - if (!TextUtils.isEmpty(stackTrace)) { - uris.add(new CrashLetterStackTraceBuilder(context).setStackTrace(stackTrace).getUri()); - } - emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); - return emailIntent; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - showCrashDialog(); - } - - private AlertDialog showCrashDialog() { - return new AlertDialog.Builder(this) - .setTitle(getString(R.string.dialog_title_application_crashed)) - .setMessage(getString(R.string.dialog_message_application_crashed)) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - finish(); - } - }) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - send( - CrashLetterActivity.this, - getIntent().getExtras().getString(YalpStoreUncaughtExceptionHandler.INTENT_MESSAGE) - ); - dialog.dismiss(); - finish(); - } - }) - .show() - ; - } -} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterBuilder.java b/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterBuilder.java deleted file mode 100644 index 398d49965..000000000 --- a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterBuilder.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.yeriomin.yalpstore; - -import android.content.Context; -import android.net.Uri; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -abstract public class CrashLetterBuilder { - - protected Context context; - - abstract protected String build(); - abstract protected File getFile(); - - public CrashLetterBuilder(Context context) { - this.context = context; - } - - public Uri getUri() { - try { - File file = getFile(); - BufferedWriter bw = new BufferedWriter(new FileWriter(file)); - bw.write(build()); - bw.close(); - return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file); - } catch (IOException e) { - return null; - } - } -} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterStackTraceBuilder.java b/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterStackTraceBuilder.java deleted file mode 100644 index c93177a65..000000000 --- a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterStackTraceBuilder.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.github.yeriomin.yalpstore; - -import android.content.Context; - -import java.io.File; -import java.io.IOException; - - -public class CrashLetterStackTraceBuilder extends CrashLetterBuilder { - - private String stackTrace; - - public CrashLetterStackTraceBuilder(Context context) { - super(context); - } - - public CrashLetterStackTraceBuilder setStackTrace(String stackTrace) { - this.stackTrace = stackTrace; - return this; - } - - @Override - protected String build() { - return stackTrace; - } - - @Override - protected File getFile() { - try { - return File.createTempFile("stacktrace-", ".txt"); - } catch (IOException e) { - return null; - } - } -} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/DebugHttpClientAdapter.java b/app/src/main/java/com/github/yeriomin/yalpstore/DebugHttpClientAdapter.java index e315c33dd..8145ea35d 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/DebugHttpClientAdapter.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/DebugHttpClientAdapter.java @@ -71,13 +71,14 @@ public class DebugHttpClientAdapter extends NativeHttpClientAdapter { } String path = new File(dumpDirectory, fileName).getAbsolutePath(); Log.i(DebugHttpClientAdapter.class.getName(), "Writing to " + path); - FileOutputStream stream; + FileOutputStream stream = null; try { stream = new FileOutputStream(path); stream.write(body); - stream.close(); } catch (IOException e) { Log.e(DebugHttpClientAdapter.class.getName(), "Could not dump request/response to " + path + ": " + e.getMessage()); + } finally { + Util.closeSilently(stream); } } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherFactory.java b/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherFactory.java index 4c968a623..84ef83d95 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherFactory.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherFactory.java @@ -23,12 +23,14 @@ public class DeltaPatcherFactory { static private boolean isGZipped(File f) { int magic = 0; + RandomAccessFile raf = null; try { - RandomAccessFile raf = new RandomAccessFile(f, "r"); + raf = new RandomAccessFile(f, "r"); magic = raf.read() & 0xff | ((raf.read() << 8) & 0xff00); - raf.close(); } catch (IOException e) { Log.e(DeltaPatcherGDiff.class.getName(), "Could not check if patch is gzipped"); + } finally { + Util.closeSilently(raf); } return magic == GZIPInputStream.GZIP_MAGIC; } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherGDiffGzipped.java b/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherGDiffGzipped.java index eb71866a4..8af2f6a57 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherGDiffGzipped.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/DeltaPatcherGDiffGzipped.java @@ -31,20 +31,23 @@ public class DeltaPatcherGDiffGzipped extends DeltaPatcherGDiff { } static private boolean GUnZip(File from, File to) { + GZIPInputStream zipInputStream = null; + FileOutputStream fileOutputStream = null; try { - GZIPInputStream zipInputStream = new GZIPInputStream(new FileInputStream(from)); - FileOutputStream fileOutputStream = new FileOutputStream(to); + zipInputStream = new GZIPInputStream(new FileInputStream(from)); + fileOutputStream = new FileOutputStream(to); byte[] buffer = new byte[0x1000]; int count; while ((count = zipInputStream.read(buffer)) != -1) { fileOutputStream.write(buffer, 0, count); } - fileOutputStream.close(); - zipInputStream.close(); return true; } catch (IOException e) { Log.e(DeltaPatcherGDiff.class.getName(), "Could not unzip the patch: " + e.getMessage()); return false; + } finally { + Util.closeSilently(fileOutputStream); + Util.closeSilently(zipInputStream); } } } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/FileProvider.java b/app/src/main/java/com/github/yeriomin/yalpstore/FileProvider.java index 9f1ad9c93..bb52ee283 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/FileProvider.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/FileProvider.java @@ -331,9 +331,6 @@ public class FileProvider extends ContentProvider { public void attachInfo(Context context, ProviderInfo info) { super.attachInfo(context, info); // Sanity check our security - if (info.exported) { - throw new SecurityException("Provider must not be exported"); - } if (!info.grantUriPermissions) { throw new SecurityException("Provider must grant uri permissions"); } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/HttpURLConnectionDownloadTask.java b/app/src/main/java/com/github/yeriomin/yalpstore/HttpURLConnectionDownloadTask.java index 54e73a099..52f3ead4a 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/HttpURLConnectionDownloadTask.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/HttpURLConnectionDownloadTask.java @@ -160,12 +160,9 @@ public class HttpURLConnectionDownloadTask extends AsyncTask staticProperties = new HashMap<>(); @@ -27,28 +29,22 @@ public class CrashLetterDeviceInfoBuilder extends CrashLetterBuilder { staticProperties.put("GL.Extensions", TextUtils.join(",", EglExtensionRetriever.getEglExtensions())); } - public CrashLetterDeviceInfoBuilder(Context context) { + public BugReportDeviceInfoBuilder(Context context) { super(context); + setFileName("device-" + Build.DEVICE + ".properties"); } @Override - public String build() { + public BugReportDeviceInfoBuilder build() { StringBuilder body = new StringBuilder(); Map deviceInfo = getDeviceInfo(); for (String key : deviceInfo.keySet()) { body.append(key).append(" = ").append(deviceInfo.get(key)).append("\n"); } body.append("\n\n"); - return body.toString(); - } - - @Override - protected File getFile() { - try { - return File.createTempFile("device-" + Build.DEVICE + "-", ".properties"); - } catch (IOException e) { - return null; - } + setContent(body.toString()); + super.build(); + return this; } private Map getDeviceInfo() { diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterLogBuilder.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportLogBuilder.java similarity index 58% rename from app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterLogBuilder.java rename to app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportLogBuilder.java index 8097d767d..13790f795 100644 --- a/app/src/main/java/com/github/yeriomin/yalpstore/CrashLetterLogBuilder.java +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportLogBuilder.java @@ -1,20 +1,21 @@ -package com.github.yeriomin.yalpstore; +package com.github.yeriomin.yalpstore.bugreport; import android.content.Context; +import com.github.yeriomin.yalpstore.bugreport.BugReportBuilder; + import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; import java.io.InputStreamReader; -public class CrashLetterLogBuilder extends CrashLetterBuilder { +public class BugReportLogBuilder extends BugReportBuilder { - public CrashLetterLogBuilder(Context context) { + public BugReportLogBuilder(Context context) { super(context); + setFileName("log.txt"); } @Override - protected String build() { + public BugReportLogBuilder build() { StringBuilder result = new StringBuilder(); Process logcat; try { @@ -27,15 +28,8 @@ public class CrashLetterLogBuilder extends CrashLetterBuilder { } catch (Exception e) { e.printStackTrace(); } - return result.toString(); - } - - @Override - protected File getFile() { - try { - return File.createTempFile("log-", ".txt"); - } catch (IOException e) { - return null; - } + setContent(result.toString()); + super.build(); + return this; } } diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportMessageBuilder.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportMessageBuilder.java new file mode 100644 index 000000000..06c58ffbf --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportMessageBuilder.java @@ -0,0 +1,33 @@ +package com.github.yeriomin.yalpstore.bugreport; + +import android.content.Context; + +import com.github.yeriomin.yalpstore.R; + +public class BugReportMessageBuilder extends BugReportBuilder { + + private String identification; + private String message; + + public BugReportMessageBuilder setIdentification(String identification) { + this.identification = identification; + return this; + } + + public BugReportMessageBuilder setMessage(String message) { + this.message = message; + return this; + } + + public BugReportMessageBuilder(Context context) { + super(context); + setFileName("message.txt"); + } + + @Override + public BugReportBuilder build() { + setContent(context.getString(R.string.bug_report_message, identification, message)); + super.build(); + return this; + } +} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportPreferencesBuilder.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportPreferencesBuilder.java new file mode 100644 index 000000000..d97a7b5b1 --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportPreferencesBuilder.java @@ -0,0 +1,55 @@ +package com.github.yeriomin.yalpstore.bugreport; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.github.yeriomin.yalpstore.PreferenceActivity; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class BugReportPreferencesBuilder extends BugReportBuilder { + + static private final String[] PREFERENCES = { + PreferenceActivity.PREFERENCE_APP_PROVIDED_EMAIL, + PreferenceActivity.PREFERENCE_AUTO_INSTALL, + PreferenceActivity.PREFERENCE_HIDE_NONFREE_APPS, + PreferenceActivity.PREFERENCE_HIDE_APPS_WITH_ADS, + PreferenceActivity.PREFERENCE_UPDATE_LIST_WHITE_OR_BLACK, + PreferenceActivity.PREFERENCE_UI_THEME, + PreferenceActivity.PREFERENCE_BACKGROUND_UPDATE_INTERVAL, + PreferenceActivity.PREFERENCE_DELETE_APK_AFTER_INSTALL, + PreferenceActivity.PREFERENCE_BACKGROUND_UPDATE_DOWNLOAD, + PreferenceActivity.PREFERENCE_BACKGROUND_UPDATE_WIFI_ONLY, + PreferenceActivity.PREFERENCE_BACKGROUND_UPDATE_INSTALL, + PreferenceActivity.PREFERENCE_REQUESTED_LANGUAGE, + PreferenceActivity.PREFERENCE_DEVICE_TO_PRETEND_TO_BE, + PreferenceActivity.PREFERENCE_INSTALLATION_METHOD, + PreferenceActivity.PREFERENCE_UPDATES_ONLY, + PreferenceActivity.PREFERENCE_SHOW_SYSTEM_APPS, + }; + + public BugReportPreferencesBuilder(Context context) { + super(context); + setFileName("preferences.txt"); + } + + @Override + public BugReportBuilder build() { + StringBuilder result = new StringBuilder(); + Map prefs = PreferenceManager.getDefaultSharedPreferences(context).getAll(); + Set whitelist = new HashSet<>(Arrays.asList(PREFERENCES)); + for (String key: prefs.keySet()) { + if (!whitelist.contains(key)) { + continue; + } + result.append(key).append(" = ").append(String.valueOf(prefs.get(key))).append("\n"); + } + setContent(result.toString()); + super.build(); + return this; + } +} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSender.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSender.java new file mode 100644 index 000000000..774ede294 --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSender.java @@ -0,0 +1,50 @@ +package com.github.yeriomin.yalpstore.bugreport; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public abstract class BugReportSender { + + protected String stackTrace; + protected String userMessage; + protected String userIdentification; + protected Context context; + protected List files = new ArrayList<>(); + + abstract public boolean send(); + + public BugReportSender setStackTrace(String stackTrace) { + this.stackTrace = stackTrace; + return this; + } + + public BugReportSender setUserMessage(String userMessage) { + this.userMessage = userMessage; + return this; + } + + public BugReportSender setUserIdentification(String userIdentification) { + this.userIdentification = userIdentification; + return this; + } + + public BugReportSender(Context context) { + this.context = context; + } + + protected void compose() { + Log.i(getClass().getName(), "Composing a report"); + files.add(new BugReportDeviceInfoBuilder(context).build().getFile()); + files.add(new BugReportLogBuilder(context).build().getFile()); + files.add(new BugReportPreferencesBuilder(context).build().getFile()); + if (!TextUtils.isEmpty(stackTrace)) { + files.add(new BugReportBuilder(context).setFileName("stacktrace.txt").setContent(stackTrace).build().getFile()); + } + } +} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderEmail.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderEmail.java new file mode 100644 index 000000000..c0612c76c --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderEmail.java @@ -0,0 +1,62 @@ +package com.github.yeriomin.yalpstore.bugreport; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; + +import com.github.yeriomin.yalpstore.BuildConfig; +import com.github.yeriomin.yalpstore.FileProvider; +import com.github.yeriomin.yalpstore.R; + +import java.io.File; +import java.util.ArrayList; + +public class BugReportSenderEmail extends BugReportSender { + + public BugReportSenderEmail(Context context) { + super(context); + } + + private Uri getUri(File file) { + return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file); + } + + @Override + public boolean send() { + compose(); + Intent emailIntent = getEmailIntent(); + if (emailIntent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(emailIntent); + return true; + } + return false; + } + + private Intent getEmailIntent() { + Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + String developerEmail = context.getString(R.string.about_developer_email); + emailIntent.setData(Uri.fromParts("mailto", developerEmail, null)); + emailIntent.setType("text/plain"); + emailIntent.setType("message/rfc822"); + emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {developerEmail}); + if (!TextUtils.isEmpty(userMessage)) { + emailIntent.putExtra(Intent.EXTRA_TEXT, userMessage); + } + emailIntent.putExtra( + Intent.EXTRA_SUBJECT, + context.getString( + TextUtils.isEmpty(stackTrace) ? R.string.email_subject_feedback : R.string.email_subject_crash_report, + BuildConfig.APPLICATION_ID, + BuildConfig.VERSION_NAME + ) + ); + ArrayList uris = new ArrayList<>(); + for (File file: files) { + uris.add(getUri(file)); + } + emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + return emailIntent; + } +} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderFtp.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderFtp.java new file mode 100644 index 000000000..9be25af64 --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportSenderFtp.java @@ -0,0 +1,105 @@ +package com.github.yeriomin.yalpstore.bugreport; + +import android.content.Context; +import android.util.Log; + +import com.github.yeriomin.yalpstore.Util; + +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +public class BugReportSenderFtp extends BugReportSender { + + static private final String FTP_HOST = "yalp-store-crash-reports.duckdns.org"; + static private final int FTP_PORT = 1021; + static private final String FTP_USER = "crashes"; + static private final String FTP_PASSWORD = "nopassword"; + + public BugReportSenderFtp(Context context) { + super(context); + } + + @Override + protected void compose() { + super.compose(); + files.add(new BugReportMessageBuilder(context).setIdentification(userIdentification).setMessage(userMessage).build().getFile()); + } + + @Override + public boolean send() { + compose(); + Log.i(BugReportSenderFtp.class.getName(), "Uploading"); + boolean result = uploadAll(); + Log.i(BugReportSenderFtp.class.getName(), "Done"); + return result; + } + + private boolean uploadAll() { + FTPClient ftpClient = new FTPClient(); + try { + ftpClient.connect(FTP_HOST, FTP_PORT); + if (!ftpClient.login(FTP_USER, FTP_PASSWORD)) { + return false; + } + String dirName = getDirName(); + ftpClient.makeDirectory(dirName); + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE, FTP.BINARY_FILE_TYPE); + ftpClient.setFileTransferMode(FTP.BINARY_FILE_TYPE); + boolean result = true; + for (File file: files) { + result &= upload(ftpClient, file, dirName + "/" + file.getName()); + } + return result; + } catch (IOException e) { + Log.e(BugReportSenderFtp.class.getName(), "FTP network error: " + e.getMessage()); + } finally { + closeSilently(ftpClient); + } + return false; + } + + static private boolean upload(FTPClient client, File file, String destination) { + FileInputStream fileInputStream; + try { + fileInputStream = new FileInputStream(file); + } catch (FileNotFoundException e) { + return false; + } + try { + return client.storeFile(destination, fileInputStream); + } catch (IOException e) { + Log.e(BugReportSenderFtp.class.getName(), client.getReplyString()); + Log.e(BugReportSenderFtp.class.getName(), "FTP network error: " + e.getMessage()); + } finally { + Util.closeSilently(fileInputStream); + } + return false; + } + + static private void closeSilently(FTPClient ftpClient) { + try { + ftpClient.logout(); + if (ftpClient.isConnected()) { + ftpClient.disconnect(); + } + } catch (IOException e) { + // Ignore + } + } + + static private String getDirName() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.getDefault()); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTimeInMillis()); + } +} diff --git a/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportService.java b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportService.java new file mode 100644 index 000000000..333a5936a --- /dev/null +++ b/app/src/main/java/com/github/yeriomin/yalpstore/bugreport/BugReportService.java @@ -0,0 +1,41 @@ +package com.github.yeriomin.yalpstore.bugreport; + +import android.app.IntentService; +import android.content.Intent; +import android.util.Log; + +public class BugReportService extends IntentService { + + static public final String INTENT_IDENTIFICATION = "INTENT_IDENTIFICATION"; + static public final String INTENT_MESSAGE = "INTENT_MESSAGE"; + static public final String INTENT_STACKTRACE = "INTENT_STACKTRACE"; + + static public final String ACTION_SEND_FTP = "ACTION_SEND_FTP"; + static public final String ACTION_SEND_EMAIL = "ACTION_SEND_EMAIL"; + + public BugReportService() { + super("BugReportService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + BugReportSender sender; + switch (intent.getAction()) { + case ACTION_SEND_FTP: + sender = new BugReportSenderFtp(getApplicationContext()); + break; + case ACTION_SEND_EMAIL: + sender = new BugReportSenderEmail(getApplicationContext()); + break; + default: + Log.e(getClass().getName(), "Unsupported action: " + intent.getAction()); + return; + } + sender + .setStackTrace(intent.getStringExtra(INTENT_STACKTRACE)) + .setUserMessage(intent.getStringExtra(INTENT_MESSAGE)) + .setUserIdentification(intent.getStringExtra(INTENT_IDENTIFICATION)) + .send() + ; + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_bug_report.png b/app/src/main/res/drawable-hdpi/ic_bug_report.png new file mode 100644 index 0000000000000000000000000000000000000000..2b448013b1300e061ed9e2a5841b2ba5483c954b GIT binary patch literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAifPk>K|t9yY@0}LEKzwjhboVg^( zFPLGw_l7?yJAvF)o-U3d5r^Mif6djPz{8qw{@cIzF%vJi?#|&0w0Q1s@S(flm``V7 zTE~L0))}%_yI4XEtwlMbL(ljrCa#ZKvge6bXgA--VW>r2pUHfU;rm5>AeYp;QImfibO_=lUpTzY*S2K9J`njxg HN@xNAc-v~% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_bug_report.png b/app/src/main/res/drawable-xhdpi/ic_bug_report.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f687a5a03282259f6ccb109fd88c8d2194f6ef GIT binary patch literal 351 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#h0(?ST8yXti3w#;8U+{khzhi~jsi{EzKTj9Okc@k8Z|3qh8St_>zSo2wSbR*z;JG!^Jkzv>@PEr1%6pGD`u}Td z$nR}$bUhJuL-T9SL}}r3`FEepS17n=EcEfuv*cYpw_;AnnLPQ#yq0O@ACJw8=bvXh zyRNExReq7N{m&U}pNiSfI%Ruoz8&2@TUKYs1?Gq%PJs{n4dq?Z@-27Ocgk$AJHWyb zuv$RDVXcz`Z^7Mo(JWCWmN_5X|C|YQ`fISHZceb>gkSwTGrvX@zxPg%Vzyv$n(h`~ voVYd6<-?)A?-{2*?z_jrC7|#i>HzPQRLRhgTe~DWM4fKh%+v literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_bug_report.png b/app/src/main/res/drawable-xxhdpi/ic_bug_report.png new file mode 100644 index 0000000000000000000000000000000000000000..fe7b5d7ce9c719701f27dad5e27cec21c71e78a8 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=I0Jk_TpJo1+zWhwq;bUVOF%*9 zk|4j}{|x0_{UU1^7#J%&T^vIyZoR#IyX&w4kL$(Bpa09>t<+?0J>#({^~#37SDKR6 zT8Ue`UNc?8DWKrcz`)4F!g1kxv&qu$-vxu5Gh|g-FRM7cR?wWICK$!;`Ou{Ef|Uo? za?h4)jzJc_N?Z6Q9m*71$jvVbQfA(Fp>`JQ`9r29^D~YH??3%6Shw=j`GxmWw%w1f zvs+iS%Kw#%wbAsim06o>7cbwE^zzG;u%5Z|giZwZ^Xn$2&oz3kmsQ?8XYbp)+(rKm zzVHh;I9YUM8MA*zL66 zg$i#OAs%_lVtAVw=ox_p>U*a{m={U}{x5)uF5f@BSHo>){5s?H3o>I)Uf;85M`ddB zXD;pIQ|F!2KDT$p$@^8e_@*9cXJ2`(^yP{Z3O}tle@%aqwV&I%@x5o+-s@Y}p8OVf tc5CwbFVD-?9X*y;1Pwi)o9efVPb@TF8hQV`C@@MGJYD@<);T3K0RVc+$k6}* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_bug_report.png b/app/src/main/res/drawable-xxxhdpi/ic_bug_report.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf54513b2edf68ae3364fa023ab5646a275d4cc GIT binary patch literal 519 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE8Azrw%`pX1oB=)|uI>ds4PcO*CCv#GWGV^r z3uah*?~70_0|Vn~PZ!6Kid%2*MEf0f5OBG;^4|YNoLveILK|);@9w(uyVGP5AAe)# z48sl>sCxOKxp;LJU*Oc^|CTwtSGWDPon_a&FE{QBym-9i_`j=6cm1l~#A{x-K3VQ} zIOmtYKrvCKzcw#7)^Bw94pgl#^5Wu2;kx=2%NEpLa7oy_-|}cieRj{)f5&7t|J~1b zY3X_Mm`!*7=LFf-Z07lHFRJtY%W|XE_xCk6RA`0$_{YmM`EPFH*?)Qpwe|nDH*_Z4 zKHvD|uU-8OufImCIG4Wv{_}9yyaig(3o02}-`l$f1bg4F-{Y{Ek?TLpo9~({CWC?p z1{%&MH<|4_d|Cbs14Bir!&`0*sLX-=Odnj>V9J2>dVU1|L24KSLmdABVM!zfd+avs z&X$S(vwz9)c&-Wcy3YTON_-5T{3M=la=oqpKUUA5;UQ-QdM4^hC`TLcJlc10;;&Sb zo}f?d`&|oW?+;7ZQQzvg?6bH~&-HjtufGrecXCM9JKu@F@9zQyCacXEe50N}lis`$ OWUZ&GpUXO@geCy|A=*Iz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_bug_report.png b/app/src/main/res/drawable/ic_bug_report.png new file mode 100644 index 0000000000000000000000000000000000000000..7a73d78821a8f5036f7f93d5f0871c6a0ab37c83 GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s`~f~8uI>dsjg5^BFc2?)?=4V@ zr6kBNm?8T4>So6s4M2Xgr;B5V$MLsQz4;C(aJaBO`vE+xai4N4pWm)CRLT4l8{T4|z4sgp!P=12EOQ(l%c&)ol!ldtJLgZLVQFYFHX zmewuZwug)xrZ)sW`>=7_uKmX}iVIpD($DN@&QYBdZMgN#g46oF+1-6RmK$7M$-1bK V+tp6`mp{-s44$rjF6*2Ung9ofSzG`B literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/bugreport_activity_layout.xml b/app/src/main/res/layout/bugreport_activity_layout.xml new file mode 100644 index 000000000..9d277a04c --- /dev/null +++ b/app/src/main/res/layout/bugreport_activity_layout.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + +