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 000000000..2b448013b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_bug_report.png differ 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 000000000..a7f687a5a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_bug_report.png differ 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 000000000..fe7b5d7ce Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_bug_report.png differ 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 000000000..5bf54513b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_bug_report.png differ 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 000000000..7a73d7882 Binary files /dev/null and b/app/src/main/res/drawable/ic_bug_report.png differ 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 @@ + + + + + + + + + + + + + + + + + + + + + +