mirror of
https://github.com/whyorean/AuroraStore.git
synced 2026-06-19 21:19:16 -04:00
Issue #241 Bug reporting reworked
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
;
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable-hdpi/ic_bug_report.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_bug_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 B |
BIN
app/src/main/res/drawable-xhdpi/ic_bug_report.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_bug_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 351 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_bug_report.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_bug_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 469 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_bug_report.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_bug_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 B |
BIN
app/src/main/res/drawable/ic_bug_report.png
Normal file
BIN
app/src/main/res/drawable/ic_bug_report.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 B |
72
app/src/main/res/layout/bugreport_activity_layout.xml
Normal file
72
app/src/main/res/layout/bugreport_activity_layout.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,5 +1,4 @@
|
||||
<paths>
|
||||
<external-path name="apks" path="Download/" />
|
||||
<cache-path name="cache" path="." />
|
||||
<root-path name="root" path="." />
|
||||
</paths>
|
||||
54
drawable-source/ic_bug_report.svg
Normal file
54
drawable-source/ic_bug_report.svg
Normal 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 |
Reference in New Issue
Block a user