Issue #241 Bug reporting reworked

This commit is contained in:
Sergey Eremin
2017-07-22 04:45:13 +03:00
parent 0850316124
commit 17068aa00d
39 changed files with 696 additions and 229 deletions

View File

@@ -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

View File

@@ -23,7 +23,7 @@
<provider
android:name="com.github.yeriomin.yalpstore.FileProvider"
android:authorities="com.github.yeriomin.yalpstore.fileprovider"
android:exported="false"
android:exported="true"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
@@ -114,9 +114,10 @@
<activity android:name=".AboutActivity" android:launchMode="singleInstance" android:configChanges="keyboardHidden|orientation|screenSize" >
<meta-data android:name="android.app.default_searchable" android:value=".SearchActivity" />
</activity>
<activity android:name=".CrashLetterActivity" android:process=":report_process" />
<activity android:name=".BugReportActivity" android:launchMode="singleInstance" android:noHistory="true" android:process=":report_process" />
<service android:enabled="true" android:name=".notification.CancelDownloadService" />
<service android:enabled="true" android:name=".notification.IgnoreUpdatesService" />
<service android:enabled="true" android:name=".bugreport.BugReportService" />
</application>
</manifest>

View File

@@ -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());

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<Uri> 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()
;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}

View File

@@ -160,12 +160,9 @@ public class HttpURLConnectionDownloadTask extends AsyncTask<String, Long, Boole
Log.e(getClass().getName(), "Could not read: " + e.getMessage());
DownloadManagerFake.putStatus(downloadId, DownloadManagerInterface.ERROR_HTTP_DATA_ERROR);
return false;
}
try {
in.close();
out.close();
} catch (IOException e) {
// Could not close
} finally {
Util.closeSilently(in);
Util.closeSilently(out);
}
return true;
}

View File

@@ -56,22 +56,12 @@ public class InstalledApkCopier {
out.write(buffer, 0, read);
}
in.close();
out.flush();
out.close();
} catch (IOException e) {
Log.e(InstalledApkCopier.class.getName(), e.getClass().getName() + " " + e.getMessage());
} finally {
try {
if (null != in) {
in.close();
}
if (null != out) {
out.close();
}
} catch (IOException e) {
// Could not close
}
Util.closeSilently(in);
Util.closeSilently(out);
}
}
}

View File

@@ -147,7 +147,8 @@ public class NativeHttpClientAdapter extends HttpClientAdapter {
outputStream.write(buffer, 0, bytesRead);
}
byte[] result = outputStream.toByteArray();
bufferedInputStream.close();
Util.closeSilently(bufferedInputStream);
Util.closeSilently(outputStream);
return result;
}

View File

@@ -6,6 +6,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import com.github.yeriomin.yalpstore.model.App;
@@ -90,12 +91,12 @@ public class UpdatableAppsTask extends GoogleApiAsyncTask {
// Comparing versions and building updatable apps list
for (App appFromMarket: appsFromPlayStore) {
String packageName = appFromMarket.getPackageName();
if (TextUtils.isEmpty(packageName)) {
if (TextUtils.isEmpty(packageName) || !installedApps.containsKey(packageName)) {
continue;
}
App installedApp = installedApps.get(packageName);
appFromMarket = addInstalledAppInfo(appFromMarket, installedApp);
if (null != installedApp && installedApp.getVersionCode() < appFromMarket.getVersionCode()) {
if (installedApp.getVersionCode() < appFromMarket.getVersionCode()) {
installedApps.remove(packageName);
updatableApps.add(appFromMarket);
} else {
@@ -103,6 +104,7 @@ public class UpdatableAppsTask extends GoogleApiAsyncTask {
}
}
return null;
// throw new RuntimeException("aaaaaa");
}
@Override

View File

@@ -7,6 +7,8 @@ import android.os.Build;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
@@ -94,4 +96,15 @@ public class Util {
return defaultValue;
}
}
static public void closeSilently(Closeable closeable) {
if (null == closeable) {
return;
}
try {
closeable.close();
} catch (IOException e) {
// Closing silently
}
}
}

View File

@@ -83,6 +83,9 @@ public abstract class YalpStoreActivity extends Activity {
case R.id.action_about:
startActivity(new Intent(this, AboutActivity.class));
break;
case R.id.action_bug_report:
startActivity(new Intent(this, BugReportActivity.class));
break;
}
return super.onOptionsItemSelected(item);
}

View File

@@ -4,9 +4,9 @@ import android.content.Context;
import android.content.Intent;
import android.util.Log;
class YalpStoreUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
import com.github.yeriomin.yalpstore.bugreport.BugReportService;
static public final String INTENT_MESSAGE = "INTENT_MESSAGE";
class YalpStoreUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private Context context;
@@ -17,11 +17,10 @@ class YalpStoreUncaughtExceptionHandler implements Thread.UncaughtExceptionHandl
@Override
public void uncaughtException(Thread t, final Throwable e) {
e.printStackTrace();
Intent errorIntent = new Intent(context.getApplicationContext(), CrashLetterActivity.class);
errorIntent.putExtra(INTENT_MESSAGE, Log.getStackTraceString(e));
errorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent errorIntent = new Intent(context.getApplicationContext(), BugReportActivity.class);
errorIntent.putExtra(BugReportService.INTENT_STACKTRACE, Log.getStackTraceString(e));
errorIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
context.getApplicationContext().startActivity(errorIntent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(2);
System.exit(0);
}
}

View File

@@ -0,0 +1,62 @@
package com.github.yeriomin.yalpstore.bugreport;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import com.github.yeriomin.yalpstore.BuildConfig;
import com.github.yeriomin.yalpstore.FileProvider;
import com.github.yeriomin.yalpstore.Util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class BugReportBuilder {
protected Context context;
protected File file;
protected String content;
public File getFile() {
return file;
}
public BugReportBuilder setFileName(String fileName) {
file = new File(context.getCacheDir(), fileName);
return this;
}
public BugReportBuilder setContent(String content) {
this.content = content;
return this;
}
public BugReportBuilder(Context context) {
this.context = context;
}
public BugReportBuilder build() {
if (null == file) {
Log.e(getClass().getName(), "No file specified");
return this;
}
file.delete();
write(content);
return this;
}
protected void write(String content) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(file));
bw.write(content);
} catch (IOException e) {
Log.e(getClass().getName(), "Could not write to temp file: " + e.getMessage());
} finally {
Util.closeSilently(bw);
}
}
}

View File

@@ -1,4 +1,4 @@
package com.github.yeriomin.yalpstore;
package com.github.yeriomin.yalpstore.bugreport;
import android.app.ActivityManager;
import android.content.Context;
@@ -9,14 +9,16 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import java.io.File;
import java.io.IOException;
import com.github.yeriomin.yalpstore.EglExtensionRetriever;
import com.github.yeriomin.yalpstore.NativeDeviceInfoProvider;
import com.github.yeriomin.yalpstore.bugreport.BugReportBuilder;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TimeZone;
public class CrashLetterDeviceInfoBuilder extends CrashLetterBuilder {
public class BugReportDeviceInfoBuilder extends BugReportBuilder {
static private Map<String, String> 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<String, String> 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<String, String> getDeviceInfo() {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, ?> prefs = PreferenceManager.getDefaultSharedPreferences(context).getAll();
Set<String> 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;
}
}

View File

@@ -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<File> 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());
}
}
}

View File

@@ -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<Uri> uris = new ArrayList<>();
for (File file: files) {
uris.add(getUri(file));
}
emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
return emailIntent;
}
}

View File

@@ -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());
}
}

View File

@@ -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()
;
}
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="10dip"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/explanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/bug_report_explanation_crash" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/bug_report_explanation_content" />
<EditText
android:id="@+id/message"
android:hint="@string/bug_report_hint_message"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/identification"
android:maxLength="30"
android:hint="@string/bug_report_hint_identification"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal" >
<Button
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:onClick="finishAndGoToHomeScreen"
android:text="@android:string/cancel" />
<Button
android:id="@+id/ok"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:onClick="sendBugReport"
android:text="@android:string/ok" />
</LinearLayout>
</LinearLayout>

View File

@@ -27,6 +27,10 @@
android:id="@+id/action_logout"
android:title="@string/action_logout"
android:icon="@drawable/ic_logout" />
<item
android:id="@+id/action_bug_report"
android:title="@string/action_bug_report"
android:icon="@drawable/ic_bug_report" />
<item
android:id="@+id/action_about"
android:title="@string/action_about"

View File

@@ -18,4 +18,5 @@
<string name="content_description_cancel_download" translatable="false">"Cancel download"</string>
<string name="email_subject_feedback" translatable="false">"%1$s %2$s User Feedback"</string>
<string name="email_subject_crash_report" translatable="false">"%1$s %2$s Crash Report"</string>
<string name="bug_report_message" translatable="false">"From: %1$s\n\nMessage:\n%2$s"</string>
</resources>

View File

@@ -36,6 +36,7 @@
<string name="action_get_local_apk">Get local apk</string>
<string name="action_make_system">Convert to system app</string>
<string name="action_make_normal">Convert to normal app</string>
<string name="action_bug_report">Report a problem</string>
<string name="search_filter">All apps</string>
<string name="search_title">Search applications</string>
<string name="dialog_title_package_id">Package with this name exists</string>
@@ -215,4 +216,9 @@
<string name="manual_download_compatible">Version code field has been pre-filled with this app\'s latest compatible version code. If you want the previous version, simply input a number smaller than the current. If the developer has not deleted the apk of the previous version from Play Store, you might be able to start the download.</string>
<string name="manual_download_warning">This page lets you manually choose the version of the app to download. Version code is a number identifying the version of an app. It starts from 1 and is incremented by the app developer every time there is a new version. Normally Play Store lets you download only the latest apk compatible with your device. By manually supplying this number you might be able to get other versions as well.\n\nKeep in mind that downloading arbitrary versions of apps is not supported by Google Play Store. Your success depends on a lucky guess of the version code. Even if you guess right and you get the download to start, you might get an incompatible apk. Most likely, you are wasting your time!</string>
<string name="manual_download_hint_version_code">version code here</string>
<string name="bug_report_hint_message">Detailed issue description</string>
<string name="bug_report_hint_identification">Your email/name/anything (optional)</string>
<string name="bug_report_explanation_content">Info about your device, Yalp Store logs and some of Yalp Store preferences will be automatically added to your message. No private or sensitive information is collected.</string>
<string name="bug_report_explanation_crash">You can help fix this by sending a bug report to the developer. Please describe what you were trying to do when the error occurred and tap OK to send it.</string>
<string name="bug_report_explanation_bug_report">Please describe the issue. Be specific if possible. Does the problem occur with a specific app? Does it happen all the time or only sometimes? How can it be reproduced?</string>
</resources>

View File

@@ -1,5 +1,4 @@
<paths>
<external-path name="apks" path="Download/" />
<cache-path name="cache" path="." />
<root-path name="root" path="." />
</paths>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="ic_bug_report.svg">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="656"
inkscape:window-height="480"
id="namedview6"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<path
d="M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z"
id="path4"
style="fill:#808080" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB