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:
parent
60d1206571
commit
bcc3d57c87
@ -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")
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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" };
|
||||
|
@ -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>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user