0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-20 12:02:16 +02:00

Add custom crash report dialog

Rename some constants and methods
This commit is contained in:
timrae 2015-01-22 03:16:56 +09:00
parent 60d1206571
commit bcc3d57c87
18 changed files with 181 additions and 814 deletions

View File

@ -34,6 +34,6 @@ dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:19.1.0'
compile 'com.google.code.gson:gson:2.3'
compile 'ch.acra:acra:4.6.0RC1'
compile 'ch.acra:acra:4.6.0RC2'
compile project(":ShowcaseView:library")
}

View File

@ -193,11 +193,7 @@
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name="com.ichi2.anki.Feedback"
android:configChanges="locale"
android:label="@string/feedback_title" />
<activity android:name="org.acra.CrashReportDialog"
<activity android:name="com.ichi2.anki.dialogs.AnkiDroidCrashReportDialog"
android:theme="@style/Theme.CrashReportDialog"
android:launchMode="singleInstance"
android:excludeFromRecents="true"

View File

@ -1319,7 +1319,7 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity {
return 0;
}
} catch (RuntimeException e) {
AnkiDroidApp.saveExceptionReportFile(e, "AbstractReviewer-getRecommendedEase");
AnkiDroidApp.sendExceptionReport(e, "AbstractReviewer-getRecommendedEase");
closeReviewer(DeckPicker.RESULT_DB_ERROR, true);
return 0;
}
@ -1589,7 +1589,7 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity {
try {
buttonCount = mSched.answerButtons(mCurrentCard);
} catch (RuntimeException e) {
AnkiDroidApp.saveExceptionReportFile(e, "AbstractReviewer-showEaseButtons");
AnkiDroidApp.sendExceptionReport(e, "AbstractReviewer-showEaseButtons");
closeReviewer(DeckPicker.RESULT_DB_ERROR, true);
return;
}

View File

@ -77,7 +77,7 @@ public class AnkiDb {
@Override
public void onCorruption(SQLiteDatabase db) {
Log.e(AnkiDroidApp.TAG, "The database has been corrupted...");
AnkiDroidApp.saveExceptionReportFile("AnkiDb.MyDbErrorHandler.onCorruption", "Db has been corrupted ");
AnkiDroidApp.sendExceptionReport("AnkiDb.MyDbErrorHandler.onCorruption", "Db has been corrupted ");
AnkiDroidApp.closeCollection(false);
}
}
@ -256,13 +256,13 @@ public class AnkiDb {
sb.append("AnkiDb.queryColumn (column " + column + "): ");
sb.append("Exception due to null. Query: " + query);
sb.append(" Null occurrences during this query: " + nullExceptionCount);
AnkiDroidApp.saveExceptionReportFile(nullException, "queryColumn_encounteredNull", sb.toString());
AnkiDroidApp.sendExceptionReport(nullException, "queryColumn_encounteredNull", sb.toString());
Log.w(AnkiDroidApp.TAG, sb.toString());
} else { // nullException not properly initialized
StringBuilder sb = new StringBuilder();
sb.append("AnkiDb.queryColumn(): Critical error -- ");
sb.append("unable to pass in the actual exception to error reporting.");
AnkiDroidApp.saveExceptionReportFile("queryColumn_encounteredNull", sb.toString());
AnkiDroidApp.sendExceptionReport("queryColumn_encounteredNull", sb.toString());
Log.e(AnkiDroidApp.TAG, sb.toString());
}
}

View File

@ -35,6 +35,7 @@ import android.view.Display;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import com.ichi2.anki.dialogs.AnkiDroidCrashReportDialog;
import com.ichi2.anki.exception.AnkiDroidErrorReportException;
import com.ichi2.async.Connection;
import com.ichi2.compat.Compat;
@ -68,7 +69,7 @@ import java.util.concurrent.locks.ReentrantLock;
* Application class.
*/
@ReportsCrashes(
formKey = "", // This is required for backward compatibility but not used
reportDialogClass = AnkiDroidCrashReportDialog.class,
httpMethod = HttpSender.Method.PUT,
reportType = HttpSender.Type.JSON,
formUri = "https://ankidroid.org/acra/report",
@ -78,7 +79,6 @@ import java.util.concurrent.locks.ReentrantLock;
resDialogText = R.string.feedback_default_text,
resToastText = R.string.feedback_auto_toast_text,
resDialogPositiveButtonText = R.string.feedback_report,
sharedPreferencesName = "com.ichi2.anki",
additionalSharedPreferences = {"com.ichi2.anki"},
excludeMatchingSharedPreferencesKeys = {"username","hkey"},
customReportContent = {
@ -130,6 +130,10 @@ public class AnkiDroidApp extends Application {
public static final String LIBANKI_VERSION = "1.2.5";
public static final String DROPBOX_PUBLIC_DIR = "/dropbox/Public/Anki";
public static final String APP_NAMESPACE = "http://schemas.android.com/apk/res/com.ichi2.anki";
public static final String FEEDBACK_REPORT_ASK = "2";
public static final String FEEDBACK_REPORT_NEVER = "1";
public static final String FEEDBACK_REPORT_ALWAYS = "0";
/**
@ -246,7 +250,7 @@ public class AnkiDroidApp extends Application {
DEFAULT_SWIPE_THRESHOLD_VELOCITY = vc.getScaledMinimumFlingVelocity();
// Set ACRA reporting mode
setAcraReportingMode(preferences.getString("reportErrorMode",Feedback.REPORT_ASK));
setAcraReportingMode(preferences.getString("reportErrorMode", FEEDBACK_REPORT_ASK));
}
@ -446,21 +450,21 @@ public class AnkiDroidApp extends Application {
}
public static void saveExceptionReportFile(String origin, String additionalInfo) {
public static void sendExceptionReport(String origin, String additionalInfo) {
try {
throw new AnkiDroidErrorReportException();
} catch (AnkiDroidErrorReportException e) {
saveExceptionReportFile(e, origin, additionalInfo);
sendExceptionReport(e, origin, additionalInfo);
}
}
public static void saveExceptionReportFile(Throwable e, String origin) {
saveExceptionReportFile(e, origin, null);
public static void sendExceptionReport(Throwable e, String origin) {
sendExceptionReport(e, origin, null);
}
public static void saveExceptionReportFile(Throwable e, String origin, String additionalInfo) {
public static void sendExceptionReport(Throwable e, String origin, String additionalInfo) {
//CustomExceptionHandler.getInstance().uncaughtException(null, e, origin, additionalInfo);
ACRA.getErrorReporter().putCustomData("origin", origin);
ACRA.getErrorReporter().putCustomData("additionalInfo", additionalInfo);
@ -605,16 +609,16 @@ public class AnkiDroidApp extends Application {
public void setAcraReportingMode(String value) {
SharedPreferences.Editor editor = getSharedPrefs(this).edit();
// Set the ACRA disable value
if (value.equals(Feedback.REPORT_NEVER)) {
if (value.equals(FEEDBACK_REPORT_NEVER)) {
editor.putBoolean("acra.disable", true);
} else {
editor.putBoolean("acra.disable", false);
// Switch between auto-report via toast and manual report via dialog
try {
if (value.equals(Feedback.REPORT_ALWAYS)) {
if (value.equals(FEEDBACK_REPORT_ALWAYS)) {
ACRA.getConfig().setMode(ReportingInteractionMode.TOAST);
ACRA.getConfig().setResToastText(R.string.feedback_auto_toast_text);
} else if (value.equals(Feedback.REPORT_ASK)) {
} else if (value.equals(FEEDBACK_REPORT_ASK)) {
ACRA.getConfig().setMode(ReportingInteractionMode.DIALOG);
ACRA.getConfig().setResToastText(R.string.feedback_manual_toast_text);
}

View File

@ -190,7 +190,7 @@ public class CramDeckOptions extends PreferenceActivity implements OnSharedPrefe
mCol.getDecks().save(mDeck);
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "CramDeckOptions - RuntimeException on saving deck: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "CramDeckOptionsSaveDeck");
AnkiDroidApp.sendExceptionReport(e, "CramDeckOptionsSaveDeck");
setResult(DeckPicker.RESULT_DB_ERROR);
finish();
}

View File

@ -286,7 +286,7 @@ public class DeckOptions extends PreferenceActivity implements OnSharedPreferenc
mCol.getDecks().save(mOptions);
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "DeckOptions - RuntimeException on saving conf: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "DeckOptionsSaveConf");
AnkiDroidApp.sendExceptionReport(e, "DeckOptionsSaveConf");
setResult(DeckPicker.RESULT_DB_ERROR);
finish();
}

View File

@ -1085,9 +1085,7 @@ public class DeckPicker extends NavigationDrawerActivity implements OnShowcaseEv
// Callback method to submit error report
@Override
public void sendErrorReport() {
Intent i = new Intent(this, Feedback.class);
i.putExtra("request", DeckPicker.RESULT_DB_ERROR);
startActivityForResultWithAnimation(i, DeckPicker.REPORT_ERROR, ActivityTransitionAnimation.RIGHT);
AnkiDroidApp.sendExceptionReport(new RuntimeException(), "DeckPicker.sendErrorReport");
}

View File

@ -1,639 +0,0 @@
/***************************************************************************************
* Copyright (c) 2011 Kostas Spyropoulos <inigo.aldana@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki;
import android.app.Application;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SimpleAdapter;
import com.ichi2.anim.ActivityTransitionAnimation;
import com.ichi2.async.Connection;
import com.ichi2.async.Connection.Payload;
import com.ichi2.libanki.Utils;
import com.ichi2.themes.StyledDialog;
import com.ichi2.themes.Themes;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
public class Feedback extends AnkiActivity {
public static String REPORT_ASK = "2";
public static String REPORT_NEVER = "1";
public static String REPORT_ALWAYS = "0";
public static String STATE_WAITING = "0";
public static String STATE_UPLOADING = "1";
public static String STATE_SUCCESSFUL = "2";
public static String STATE_FAILED = "3";
public static String TYPE_STACKTRACE = "crash-stacktrace";
public static String TYPE_FEEDBACK = "feedback";
public static String TYPE_ERROR_FEEDBACK = "error-feedback";
public static String TYPE_OTHER_ERROR = "other-error";
protected static SimpleDateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
protected static SimpleDateFormat df2 = new SimpleDateFormat("Z", Locale.US);
protected static TimeZone localTz = TimeZone.getDefault();
// This is used to group the batch of bugs and notes sent on the server side
protected long mNonce;
protected List<HashMap<String, String>> mErrorReports;
protected SimpleAdapter mErrorAdapter;
protected ListView mLvErrorList;
protected EditText mEtFeedbackText;
protected boolean mPostingFeedback;
protected InputMethodManager mImm = null;
protected StyledDialog mNoConnectionAlert = null;
protected String mReportErrorMode;
protected String mFeedbackUrl;
protected String mErrorUrl;
private boolean mAllowFeedback;
private boolean mErrorsSent = false;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
deleteFiles(true, false);
closeFeedback();
}
return super.onKeyDown(keyCode, event);
}
/**
* Create AlertDialogs used on all the activity
*/
private void initAllAlertDialogs() {
Resources res = getResources();
StyledDialog.Builder builder = new StyledDialog.Builder(this);
// builder.setTitle(res.getString(R.string.connection_error_title));
builder.setIcon(R.drawable.ic_dialog_alert);
builder.setMessage(res.getString(R.string.youre_offline));
builder.setPositiveButton(res.getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mPostingFeedback = false;
refreshInterface();
}
});
mNoConnectionAlert = builder.create();
}
private void closeFeedback() {
if (getIntent().getIntExtra("request", 0) == DeckPicker.RESULT_DB_ERROR) {
setResult(DeckPicker.RESULT_DB_ERROR);
} else {
setResult(RESULT_OK);
}
finish();
ActivityTransitionAnimation.slide(Feedback.this, ActivityTransitionAnimation.LEFT);
}
private void refreshInterface() {
if (mAllowFeedback) {
Resources res = getResources();
Button btnSend = (Button) findViewById(R.id.btnFeedbackSend);
Button btnKeepLatest = (Button) findViewById(R.id.btnFeedbackKeepLatest);
Button btnClearAll = (Button) findViewById(R.id.btnFeedbackClearAll);
ProgressBar pbSpinner = (ProgressBar) findViewById(R.id.pbFeedbackSpinner);
int numErrors = mErrorReports.size();
if (numErrors == 0 || mErrorsSent) {
if (!mErrorsSent) {
mLvErrorList.setVisibility(View.GONE);
}
btnKeepLatest.setVisibility(View.GONE);
btnClearAll.setVisibility(View.GONE);
btnSend.setText(res.getString(R.string.feedback_send_feedback));
} else {
mLvErrorList.setVisibility(View.VISIBLE);
btnKeepLatest.setVisibility(View.VISIBLE);
btnClearAll.setVisibility(View.VISIBLE);
btnSend.setText(res.getString(R.string.feedback_send_feedback_and_errors));
refreshErrorListView();
if (numErrors == 1) {
btnKeepLatest.setEnabled(false);
} else {
btnKeepLatest.setEnabled(true);
}
}
if (mPostingFeedback) {
int buttonHeight = btnSend.getHeight();
btnSend.setVisibility(View.GONE);
pbSpinner.setVisibility(View.VISIBLE);
LinearLayout topLine = (LinearLayout) findViewById(R.id.llFeedbackTopLine);
topLine.setMinimumHeight(buttonHeight);
mEtFeedbackText.setEnabled(false);
mImm.hideSoftInputFromWindow(mEtFeedbackText.getWindowToken(), 0);
} else {
btnSend.setVisibility(View.VISIBLE);
pbSpinner.setVisibility(View.GONE);
mEtFeedbackText.setEnabled(true);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Themes.applyTheme(this);
super.onCreate(savedInstanceState);
Resources res = getResources();
Context context = getBaseContext();
SharedPreferences sharedPreferences = AnkiDroidApp.getSharedPrefs(context);
mReportErrorMode = sharedPreferences.getString("reportErrorMode", REPORT_ASK);
mNonce = UUID.randomUUID().getMostSignificantBits();
mFeedbackUrl = res.getString(R.string.feedback_post_url);
mErrorUrl = res.getString(R.string.error_post_url);
mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
mPostingFeedback = false;
initAllAlertDialogs();
getErrorFiles();
Intent i = getIntent();
mAllowFeedback = (i.hasExtra("request") && (i.getIntExtra("request", 0) == DeckPicker.REPORT_FEEDBACK || i
.getIntExtra("request", 0) == DeckPicker.RESULT_DB_ERROR)) || mReportErrorMode.equals(REPORT_ASK);
if (!mAllowFeedback) {
if (mReportErrorMode.equals(REPORT_ALWAYS)) { // Always report
try {
String feedback = "Automatically sent";
Connection.sendFeedback(mSendListener, new Payload(new Object[] { mFeedbackUrl, mErrorUrl,
feedback, mErrorReports, mNonce, getApplication(), true }));
if (mErrorReports.size() > 0) {
mPostingFeedback = true;
}
if (feedback.length() > 0) {
mPostingFeedback = true;
}
} catch (Exception e) {
Log.e(AnkiDroidApp.TAG, e.toString());
}
finish();
ActivityTransitionAnimation.slide(Feedback.this, ActivityTransitionAnimation.NONE);
return;
} else if (mReportErrorMode.equals(REPORT_NEVER)) { // Never report
deleteFiles(false, false);
finish();
ActivityTransitionAnimation.slide(Feedback.this, ActivityTransitionAnimation.NONE);
return;
}
}
View mainView = getLayoutInflater().inflate(R.layout.feedback, null);
setContentView(mainView);
Themes.setWallpaper(mainView);
Themes.setTextViewStyle(findViewById(R.id.tvFeedbackDisclaimer));
Themes.setTextViewStyle(findViewById(R.id.lvFeedbackErrorList));
Button btnSend = (Button) findViewById(R.id.btnFeedbackSend);
Button btnKeepLatest = (Button) findViewById(R.id.btnFeedbackKeepLatest);
Button btnClearAll = (Button) findViewById(R.id.btnFeedbackClearAll);
mEtFeedbackText = (EditText) findViewById(R.id.etFeedbackText);
mLvErrorList = (ListView) findViewById(R.id.lvFeedbackErrorList);
mErrorAdapter = new SimpleAdapter(this, mErrorReports, R.layout.error_item, new String[] { "name", "state",
"result" }, new int[] { R.id.error_item_text, R.id.error_item_progress, R.id.error_item_status });
mErrorAdapter.setViewBinder(new SimpleAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Object arg1, String text) {
switch (view.getId()) {
case R.id.error_item_progress:
if (text.equals(STATE_UPLOADING)) {
view.setVisibility(View.VISIBLE);
} else {
view.setVisibility(View.GONE);
}
return true;
case R.id.error_item_status:
if (text.length() == 0) {
view.setVisibility(View.GONE);
return true;
} else {
view.setVisibility(View.VISIBLE);
return false;
}
}
return false;
}
});
btnClearAll.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
deleteFiles(false, false);
refreshErrorListView();
refreshInterface();
}
});
mLvErrorList.setAdapter(mErrorAdapter);
btnSend.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!mPostingFeedback) {
String feedback = mEtFeedbackText.getText().toString();
Connection.sendFeedback(mSendListener, new Payload(new Object[] { mFeedbackUrl, mErrorUrl,
feedback, mErrorReports, mNonce, getApplication(), false }));
if (mErrorReports.size() > 0) {
mPostingFeedback = true;
}
if (feedback.length() > 0) {
mPostingFeedback = true;
}
refreshInterface();
}
}
});
btnKeepLatest.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
deleteFiles(false, true);
refreshErrorListView();
refreshInterface();
}
});
refreshInterface();
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
closeFeedback();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void refreshErrorListView() {
if (mAllowFeedback) {
mErrorAdapter.notifyDataSetChanged();
}
}
private void getErrorFiles() {
mErrorReports = new ArrayList<HashMap<String, String>>();
String[] errors = fileList();
for (String file : errors) {
if (file.endsWith(".stacktrace")) {
HashMap<String, String> error = new HashMap<String, String>();
error.put("filename", file);
error.put("name", file);
error.put("state", STATE_WAITING);
error.put("result", "");
mErrorReports.add(error);
}
}
}
/**
* Delete the crash log files.
*
* @param onlyProcessed only delete the log files that have been sent.
* @param keepLatest keep the latest log file. If the file has not been sent yet, it is not deleted even if this
* value is set to false.
*/
private void deleteFiles(boolean onlyProcessed, boolean keepLatest) {
for (int i = (keepLatest ? 1 : 0); i < mErrorReports.size();) {
try {
String errorState = mErrorReports.get(i).get("state");
if (!onlyProcessed || errorState.equals(STATE_SUCCESSFUL)) {
deleteFile(mErrorReports.get(i).get("filename"));
mErrorReports.remove(i);
} else {
i++;
}
} catch (Exception e) {
Log.e(AnkiDroidApp.TAG, String.format("Could not delete file: %s", mErrorReports.get(i)));
}
}
}
public static boolean isErrorType(String postType) {
return !(postType.equals(TYPE_FEEDBACK) || postType.equals(TYPE_ERROR_FEEDBACK));
}
Connection.TaskListener mSendListener = new Connection.TaskListener() {
@Override
public void onDisconnected() {
if (mNoConnectionAlert != null) {
mNoConnectionAlert.show();
}
}
@Override
public void onPostExecute(Payload data) {
mPostingFeedback = false;
mErrorsSent = true;
refreshInterface();
}
@Override
public void onPreExecute() {
// pass
}
@Override
public void onProgressUpdate(Object... values) {
Resources res = getResources();
String postType = (String) values[0];
int errorIndex = (Integer) values[1];
String state = (String) values[2];
if (isErrorType(postType) && mErrorReports.size() > errorIndex) {
mErrorReports.get(errorIndex).put("state", state);
if (!state.equals(Feedback.STATE_UPLOADING)) {
int returnCode = (Integer) values[3];
if (returnCode == 200) {
// The result is either: "new" (for first encountered bug), "known" (for existing bugs) or
// ("issue:xxx:<status>" for known and linked)
String result = (String) values[4];
if (result.equalsIgnoreCase("new")) {
mErrorReports.get(errorIndex).put("name", res.getString(R.string.feedback_error_reply_new));
} else if (result.equalsIgnoreCase("known")) {
mErrorReports.get(errorIndex).put("name",
res.getString(R.string.feedback_error_reply_known));
} else if (result.startsWith("issue:")) {
String[] resultPieces = result.split(":");
int issue = Integer.parseInt(resultPieces[1]);
String status = "";
if (resultPieces.length > 1) {
if (resultPieces.length > 2) {
status = resultPieces[2];
}
if (status.length() == 0) {
mErrorReports.get(errorIndex).put("name",
res.getString(R.string.feedback_error_reply_issue_unknown, issue));
} else if (status.equalsIgnoreCase("fixed")) {
mErrorReports.get(errorIndex).put("name",
res.getString(R.string.feedback_error_reply_issue_fixed_prod, issue));
} else if (status.equalsIgnoreCase("fixedindev")) {
mErrorReports.get(errorIndex).put("name",
res.getString(R.string.feedback_error_reply_issue_fixed_dev, issue));
} else {
mErrorReports.get(errorIndex).put("name",
res.getString(R.string.feedback_error_reply_issue_status, issue, status));
}
} else {
mErrorReports.get(errorIndex).put("result",
res.getString(R.string.feedback_error_reply_malformed));
}
} else {
mErrorReports.get(errorIndex).put("result",
res.getString(R.string.feedback_error_reply_malformed));
}
} else {
mErrorReports.get(errorIndex)
.put("result", res.getString(R.string.feedback_error_reply_failed));
}
}
refreshErrorListView();
} else {
if (mAllowFeedback) {
if (state.equals(STATE_SUCCESSFUL)) {
mEtFeedbackText.setText("");
Themes.showThemedToast(Feedback.this, res.getString(R.string.feedback_message_sent_success),
false);
} else if (state.equals(STATE_FAILED)) {
int respCode = (Integer) values[3];
if (respCode == 0) {
onDisconnected();
} else {
Themes.showThemedToast(Feedback.this,
res.getString(R.string.feedback_message_sent_failure, respCode), false);
}
}
}
}
}
};
// Run in AsyncTask
private static void addTimestamp(List<NameValuePair> pairs) {
Date ts = new Date();
df1.setTimeZone(TimeZone.getTimeZone("UTC"));
String reportsentutc = String.format("%s", df1.format(ts));
String reportsenttzoffset = String.format("%s", df2.format(ts));
String reportsenttz;
if (AnkiDroidApp.isChromebook()) {
// Chrome creates timezone strings such as GMT+5, which are not supported
// by ankidroid-triage (pytz) and result in a 500 error. This is something that
// should be fixed on the server-side. In the meantime, I would much rather have
// issue reports in the wrong timezone than no issue reports at all.
reportsenttz = "GMT";
} else {
reportsenttz = String.format("%s", localTz.getID());
}
pairs.add(new BasicNameValuePair("reportsentutc", reportsentutc));
pairs.add(new BasicNameValuePair("reportsenttzoffset", reportsenttzoffset));
pairs.add(new BasicNameValuePair("reportsenttz", reportsenttz));
}
private static List<NameValuePair> extractPairsFromError(String type, String errorFile, String groupId, int index,
Application app) {
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
pairs.add(new BasicNameValuePair("type", "crash-stacktrace"));
pairs.add(new BasicNameValuePair("groupid", groupId));
pairs.add(new BasicNameValuePair("index", String.valueOf(index)));
addTimestamp(pairs);
String singleLine = null;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(app.openFileInput(errorFile)));
while ((singleLine = br.readLine()) != null) {
int indexOfEquals = singleLine.indexOf('=');
if (indexOfEquals == -1) {
continue;
}
String key = singleLine.substring(0, indexOfEquals).toLowerCase(Locale.US);
String value = singleLine.substring(indexOfEquals + 1, singleLine.length());
if (key.equals("stacktrace")) {
StringBuilder sb = new StringBuilder(value);
while ((singleLine = br.readLine()) != null) {
sb.append(singleLine);
sb.append("\n");
}
value = sb.toString();
}
pairs.add(new BasicNameValuePair(key, value));
}
br.close();
} catch (FileNotFoundException e) {
Log.w(AnkiDroidApp.TAG, "Couldn't open crash report " + errorFile);
return null;
} catch (IOException e) {
Log.w(AnkiDroidApp.TAG, "Couldn't read crash report " + errorFile);
return null;
}
return pairs;
}
/**
* Posting feedback or error info to the server. This is called from the AsyncTask.
*
* @param url The url to post the feedback to.
* @param type The type of the info, eg Feedback.TYPE_CRASH_STACKTRACE.
* @param feedback For feedback types this is the message. For error/crash types this is the path to the error file.
* @param groupId A single time generated ID, so that errors/feedback send together can be grouped together.
* @param index The index of the error in the list
* @return A Payload file showing success, response code and response message.
*/
public static Payload postFeedback(String url, String type, String feedback, String groupId, int index,
Application app) {
Payload result = new Payload(null);
List<NameValuePair> pairs = null;
if (!isErrorType(type)) {
pairs = new ArrayList<NameValuePair>();
pairs.add(new BasicNameValuePair("type", type));
pairs.add(new BasicNameValuePair("groupid", groupId));
pairs.add(new BasicNameValuePair("index", "0"));
pairs.add(new BasicNameValuePair("message", feedback));
addTimestamp(pairs);
} else {
pairs = Feedback.extractPairsFromError(type, feedback, groupId, index, app);
if (pairs == null) {
result.success = false;
result.result = null;
}
}
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("User-Agent", "AnkiDroid");
try {
httpPost.setEntity(new UrlEncodedFormEntity(pairs));
HttpResponse response = httpClient.execute(httpPost);
Log.e(AnkiDroidApp.TAG, String.format("Bug report posted to %s", url));
int respCode = response.getStatusLine().getStatusCode();
switch (respCode) {
case 200:
result.success = true;
result.returnType = respCode;
result.result = Utils.convertStreamToString(response.getEntity().getContent());
Log.i(AnkiDroidApp.TAG, String.format("postFeedback OK: %s", result.result));
break;
default:
Log.e(AnkiDroidApp.TAG, String.format("postFeedback failure: %d - %s", response.getStatusLine()
.getStatusCode(), response.getStatusLine().getReasonPhrase()));
result.success = false;
result.returnType = respCode;
result.result = response.getStatusLine().getReasonPhrase();
break;
}
} catch (ClientProtocolException ex) {
Log.e(AnkiDroidApp.TAG, "ClientProtocolException: " + ex.toString());
result.success = false;
result.result = ex.toString();
} catch (IOException ex) {
Log.e(AnkiDroidApp.TAG, "IOException: " + ex.toString());
result.success = false;
result.result = ex.toString();
}
return result;
}
}

View File

@ -89,15 +89,15 @@ public class IntentHandler extends Activity {
successful = sendShowImportFileDialogMsg(importUri.getEncodedPath());
} catch (FileNotFoundException e) {
errorMessage=e.getLocalizedMessage();
AnkiDroidApp.saveExceptionReportFile(e, "IntentHandler.java", "apkg import failed: " + filename);
AnkiDroidApp.sendExceptionReport(e, "IntentHandler.java", "apkg import failed: " + filename);
} catch (IOException e2) {
errorMessage=e2.getLocalizedMessage();
AnkiDroidApp.saveExceptionReportFile(e2, "IntentHandler.java", "apkg import failed" + filename);
AnkiDroidApp.sendExceptionReport(e2, "IntentHandler.java", "apkg import failed" + filename);
}
} else {
if (filename == null) {
errorMessage = "Could not retrieve filename from content resolver; try opening the apkg file with a file explorer";
AnkiDroidApp.saveExceptionReportFile("IntentHandler.java", "apkg import failed (filename null, no MIME type provided)");
AnkiDroidApp.sendExceptionReport("IntentHandler.java", "apkg import failed (filename null, no MIME type provided)");
} else {
errorMessage = getResources().getString(R.string.import_error_not_apkg_extension, filename);
}

View File

@ -0,0 +1,119 @@
/***************************************************************************************
* Copyright (c) 2015 Timothy Rae <perceptualchaos2@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki.dialogs;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.R;
import org.acra.ACRA;
import org.acra.BaseCrashReportDialog;
public class AnkiDroidCrashReportDialog extends BaseCrashReportDialog implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
private static final String STATE_COMMENT = "comment";
CheckBox mAlwaysReportCheckBox;
EditText mUserComment;
AlertDialog mDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
int resourceId = ACRA.getConfig().resDialogTitle();
if(resourceId != 0) {
dialogBuilder.setTitle(resourceId);
}
resourceId = ACRA.getConfig().resDialogIcon();
if(resourceId != 0) {
dialogBuilder.setIcon(resourceId);
}
dialogBuilder.setView(buildCustomView(savedInstanceState));
dialogBuilder.setPositiveButton(getText(ACRA.getConfig().resDialogPositiveButtonText()), AnkiDroidCrashReportDialog.this);
dialogBuilder.setNegativeButton(getText(ACRA.getConfig().resDialogNegativeButtonText()), AnkiDroidCrashReportDialog.this);
mDialog = dialogBuilder.create();
mDialog.setCanceledOnTouchOutside(false);
mDialog.setOnDismissListener(this);
mDialog.show();
}
/**
* Build the custom view used by the dialog
* @param savedInstanceState
* @return
*/
private View buildCustomView(Bundle savedInstanceState) {
SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(this);
LayoutInflater inflater = getLayoutInflater();
View rootView = inflater.inflate(R.layout.feedback, null);
mAlwaysReportCheckBox = (CheckBox) rootView.findViewById(R.id.alwaysReportCheckbox);
mAlwaysReportCheckBox.setChecked(preferences.getBoolean("autoreportCheckboxValue", true));
mUserComment = (EditText) rootView.findViewById(R.id.etFeedbackText);
// Set user comment if reloading after the activity has been stopped
if (savedInstanceState != null) {
String savedValue = savedInstanceState.getString(STATE_COMMENT);
if (savedValue != null) {
mUserComment.setText(savedValue);
}
}
return rootView;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
// Next time don't tick the auto-report checkbox by default
boolean autoReport = mAlwaysReportCheckBox.isChecked();
SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(this);
preferences.edit().putBoolean("autoreportCheckboxValue", autoReport).commit();
// Set the autoreport value to true if ticked
if (autoReport) {
preferences.edit().putString("reportErrorMode", AnkiDroidApp.FEEDBACK_REPORT_ALWAYS).commit();
AnkiDroidApp.getInstance().setAcraReportingMode(AnkiDroidApp.FEEDBACK_REPORT_ALWAYS);
}
// Send the crash report
sendCrash(mUserComment.getText().toString(), "");
} else {
cancelReports();
}
finish();
}
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mUserComment != null && mUserComment.getText() != null) {
outState.putString(STATE_COMMENT, mUserComment.getText().toString());
}
}
}

View File

@ -7,8 +7,6 @@ import android.util.Log;
import com.ichi2.anki.AnkiActivity;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.BackupManager;
import com.ichi2.anki.R;
import com.ichi2.libanki.Collection;
import java.lang.ref.WeakReference;
@ -35,7 +33,7 @@ public class CollectionLoader extends AsyncTaskLoader<Collection> {
return AnkiDroidApp.openCollection(colPath);
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundOpenCollection - RuntimeException on opening collection: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundOpenCollection");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundOpenCollection");
return null;
}
}

View File

@ -18,7 +18,6 @@
package com.ichi2.async;
import android.app.Application;
import android.content.Context;
import android.database.Cursor;
import android.net.ConnectivityManager;
@ -27,7 +26,6 @@ import android.os.AsyncTask;
import android.util.Log;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.Feedback;
import com.ichi2.anki.R;
import com.ichi2.anki.exception.UnknownHttpResponseException;
import com.ichi2.anki.exception.UnsupportedSyncException;
@ -65,7 +63,6 @@ import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@ -77,7 +74,7 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
public static final int TASK_TYPE_SYNC = 1;
public static final int TASK_TYPE_GET_SHARED_DECKS = 2;
public static final int TASK_TYPE_GET_PERSONAL_DECKS = 3;
public static final int TASK_TYPE_SEND_CRASH_REPORT = 4;
//public static final int TASK_TYPE_SEND_CRASH_REPORT = 4;
public static final int TASK_TYPE_DOWNLOAD_MEDIA = 5;
public static final int TASK_TYPE_REGISTER = 6;
public static final int TASK_TYPE_UPGRADE_DECKS = 7;
@ -216,12 +213,6 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
}
public static Connection sendFeedback(TaskListener listener, Payload data) {
data.taskType = TASK_TYPE_SEND_CRASH_REPORT;
return launchConnectionTask(listener, data);
}
public static Connection downloadMissingMedia(TaskListener listener, Payload data) {
data.taskType = TASK_TYPE_DOWNLOAD_MEDIA;
return launchConnectionTask(listener, data);
@ -270,9 +261,6 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
case TASK_TYPE_SYNC:
return doInBackgroundSync(data);
case TASK_TYPE_SEND_CRASH_REPORT:
return doInBackgroundSendFeedback(data);
case TASK_TYPE_DOWNLOAD_MEDIA:
return doInBackgroundDownloadMissingMedia(data);
@ -302,7 +290,7 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
} catch (Exception e2) {
// Ask user to report all bugs which aren't timeout errors
if (!timeoutOccured(e2)) {
AnkiDroidApp.saveExceptionReportFile(e2, "doInBackgroundLogin");
AnkiDroidApp.sendExceptionReport(e2, "doInBackgroundLogin");
}
data.success = false;
data.result = new Object[] {"connectionError" };
@ -496,7 +484,7 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
}
col = AnkiDroidApp.openCollection(path);
} catch (OutOfMemoryError e) {
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSync-fullSync");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
data.success = false;
data.result = new Object[] { "OutOfMemoryError" };
data.data = new Object[] { mediaUsn };
@ -505,7 +493,7 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
if (timeoutOccured(e)) {
data.result = new Object[] {"connectionError" };
} else {
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSync-fullSync");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
data.result = new Object[] { "IOException" };
}
data.success = false;
@ -543,12 +531,12 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
} catch (UnsupportedSyncException e) {
mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_unsupported);
AnkiDroidApp.getSharedPrefs(sContext).edit().putBoolean("syncFetchesMedia", false).commit();
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSync-mediaSync");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-mediaSync");
} catch (RuntimeException e) {
if (timeoutOccured(e)) {
data.result = new Object[] {"connectionError" };
} else {
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSync-mediaSync");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-mediaSync");
}
mediaError = e.getLocalizedMessage();
}
@ -582,7 +570,7 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
if (timeoutOccured(e)) {
data.result = new Object[] {"connectionError" };
} else {
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSync");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
data.result = new Object[] {e.getLocalizedMessage()};
}
return data;
@ -613,56 +601,6 @@ public class Connection extends BaseAsyncTask<Connection.Payload, Object, Connec
}
private Payload doInBackgroundSendFeedback(Payload data) {
Log.i(AnkiDroidApp.TAG, "doInBackgroundSendFeedback");
String feedbackUrl = (String) data.data[0];
String errorUrl = (String) data.data[1];
String feedback = (String) data.data[2];
ArrayList<HashMap<String, String>> errors = (ArrayList<HashMap<String, String>>) data.data[3];
String groupId = data.data[4].toString();
Application app = (Application) data.data[5];
boolean deleteAfterSending = (Boolean) data.data[6];
String postType = null;
if (feedback.length() > 0) {
if (errors.size() > 0) {
postType = Feedback.TYPE_ERROR_FEEDBACK;
} else {
postType = Feedback.TYPE_FEEDBACK;
}
publishProgress(postType, 0, Feedback.STATE_UPLOADING);
Payload reply = Feedback.postFeedback(feedbackUrl, postType, feedback, groupId, 0, null);
if (reply.success) {
publishProgress(postType, 0, Feedback.STATE_SUCCESSFUL, reply.returnType, reply.result);
} else {
publishProgress(postType, 0, Feedback.STATE_FAILED, reply.returnType, reply.result);
}
}
for (int i = 0; i < errors.size(); i++) {
HashMap<String, String> error = errors.get(i);
if (error.containsKey("state") && error.get("state").equals(Feedback.STATE_WAITING)) {
postType = Feedback.TYPE_STACKTRACE;
publishProgress(postType, i, Feedback.STATE_UPLOADING);
Payload reply = Feedback.postFeedback(errorUrl, postType, error.get("filename"), groupId, i, app);
if (reply.success) {
publishProgress(postType, i, Feedback.STATE_SUCCESSFUL, reply.returnType, reply.result);
} else {
publishProgress(postType, i, Feedback.STATE_FAILED, reply.returnType, reply.result);
}
if (deleteAfterSending && (reply.success || reply.returnType == 200)) {
File file = new File(app.getFilesDir() + "/" + error.get("filename"));
file.delete();
}
}
}
app = null;
return data;
}
/**
* Downloads any missing media files according to the mediaURL deckvar.
*

View File

@ -34,7 +34,6 @@ import com.ichi2.anki.exception.ConfirmModSchemaException;
import com.ichi2.libanki.AnkiPackageExporter;
import com.ichi2.libanki.Card;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Models;
import com.ichi2.libanki.Note;
import com.ichi2.libanki.Sched;
import com.ichi2.libanki.Stats;
@ -385,7 +384,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundAddNote - RuntimeException on adding fact: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundAddNote");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundAddNote");
return new TaskData(false);
}
return new TaskData(true);
@ -432,7 +431,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundUpdateNote - RuntimeException on updating fact: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundUpdateNote");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundUpdateNote");
return new TaskData(false);
}
return new TaskData(true);
@ -465,7 +464,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundAnswerCard - RuntimeException on answering card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundAnswerCard");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundAnswerCard");
return new TaskData(false);
}
return new TaskData(true);
@ -579,7 +578,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundSuspendCard - RuntimeException on suspending card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundSuspendCard");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSuspendCard");
return new TaskData(false);
}
return new TaskData(true);
@ -611,7 +610,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundMarkCard - RuntimeException on marking card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundMarkCard");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundMarkCard");
return new TaskData(false);
}
return new TaskData(true);
@ -646,7 +645,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundUndo - RuntimeException on undoing: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundUndo");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundUndo");
return new TaskData(false);
}
return new TaskData(true);
@ -857,11 +856,11 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
return new TaskData(addedCount, result.getObjArray(), true);
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundImportAdd - RuntimeException on importing cards: ", e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundImportAdd");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportAdd");
return new TaskData(false);
} catch (IOException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundImportAdd - IOException on importing cards: ", e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundImportAdd");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportAdd");
return new TaskData(false);
}
}
@ -888,7 +887,7 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
zip = new ZipFile(new File(path), ZipFile.OPEN_READ);
} catch (IOException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundImportReplace - Error while unzipping: ", e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundImportReplace0");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace0");
return new TaskData(false);
}
if (!Utils.unzipFiles(zip, fileDir, new String[] { "collection.anki2", "media" }, null)
@ -976,15 +975,15 @@ public class DeckTask extends BaseAsyncTask<DeckTask.TaskData, DeckTask.TaskData
return new TaskData(addedCount, result.getObjArray(), true);
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundImportReplace - RuntimeException: ", e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundImportReplace1");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace1");
return new TaskData(false);
} catch (FileNotFoundException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundImportReplace - FileNotFoundException: ", e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundImportReplace2");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace2");
return new TaskData(false);
} catch (IOException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundImportReplace - IOException: ", e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundImportReplace3");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundImportReplace3");
return new TaskData(false);
}
}

View File

@ -318,7 +318,7 @@ public class Collection {
lock();
}
} catch (RuntimeException e) {
AnkiDroidApp.saveExceptionReportFile(e, "closeDB");
AnkiDroidApp.sendExceptionReport(e, "closeDB");
}
AnkiDatabaseManager.closeDatabase(mPath);
mDb = null;
@ -1519,7 +1519,7 @@ public class Collection {
}
} catch (RuntimeException e) {
Log.e(AnkiDroidApp.TAG, "doInBackgroundCheckDatabase - RuntimeException on marking card: " + e);
AnkiDroidApp.saveExceptionReportFile(e, "doInBackgroundCheckDatabase");
AnkiDroidApp.sendExceptionReport(e, "doInBackgroundCheckDatabase");
return -1;
}
// and finally, optimize

View File

@ -223,10 +223,10 @@ public class Syncer {
} catch (IllegalStateException e) {
throw new RuntimeException(e);
} catch (OutOfMemoryError e) {
AnkiDroidApp.saveExceptionReportFile(e, "Syncer-sync");
AnkiDroidApp.sendExceptionReport(e, "Syncer-sync");
return new Object[] { "OutOfMemoryError" };
} catch (IOException e) {
AnkiDroidApp.saveExceptionReportFile(e, "Syncer-sync");
AnkiDroidApp.sendExceptionReport(e, "Syncer-sync");
return new Object[] { "IOException" };
}
return new Object[] { "success" };

View File

@ -4,36 +4,11 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingBottom="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingTop="10dp" >
<LinearLayout
android:id="@+id/llFeedbackTopLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:orientation="horizontal" >
<Button
android:id="@+id/btnFeedbackSend"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="@+id/feedback_send_feedback_and_errors" />
<ProgressBar
android:id="@+id/pbFeedbackSpinner"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:indeterminate="true"
android:visibility="gone" />
</LinearLayout>
<ScrollView
android:id="@+id/scrollView1"
android:layout_width="fill_parent"
@ -48,36 +23,13 @@
android:minLines="2" />
</ScrollView>
<LinearLayout
android:id="@+id/lvButtons"
<CheckBox
android:id="@+id/alwaysReportCheckbox"
android:text="@+string/feedback_report_automatically"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/btnFeedbackKeepLatest"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_gravity="center|center_vertical"
android:layout_weight="1"
android:gravity="center"
android:text="@string/feedback_keep_latest" />
<Button
android:id="@+id/btnFeedbackClearAll"
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="@string/feedback_clear_all" />
</LinearLayout>
<ListView
android:id="@+id/lvFeedbackErrorList"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:fastScrollEnabled="true" />
android:paddingTop="15dp"
android:paddingBottom="15dp"/>
<ScrollView
android:layout_width="fill_parent"
@ -87,11 +39,12 @@
android:fillViewport="true" >
<TextView
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:id="@+id/tvFeedbackDisclaimer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@+string/feedback_disclaimer"
android:textColor="#000000" />
android:text="@+string/feedback_disclaimer" />
</ScrollView>
</LinearLayout>

View File

@ -54,5 +54,6 @@
<string name="feedback_manual_toast_text">AnkiDroid has encountered a problem; a report is being generated…</string>
<string name="feedback_copy_debug">Copy debug info</string>
<string name="feedback_report">Report</string>
<string name="feedback_report_automatically">Send error reports automatically</string>
</resources>