share rather than save log files (OKC-01-015)

This commit is contained in:
Vincent Breitmoser 2015-09-11 03:08:53 +02:00
parent b76aa7fe11
commit cdf67c3296
4 changed files with 99 additions and 189 deletions

View file

@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.operations.results;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
@ -52,6 +53,8 @@ import java.util.List;
*/
public abstract class OperationResult implements Parcelable {
final static String INDENTATION_WHITESPACE = " ";
public static final String EXTRA_RESULT = "operation_result";
/**
@ -166,6 +169,27 @@ public abstract class OperationResult implements Parcelable {
", mIndent=" + mIndent +
'}';
}
StringBuilder getPrintableLogEntry(Resources resources, int indent) {
StringBuilder result = new StringBuilder();
int padding = mIndent +indent;
if (padding > INDENTATION_WHITESPACE.length()) {
padding = INDENTATION_WHITESPACE.length();
}
result.append(INDENTATION_WHITESPACE, 0, padding);
result.append(LOG_LEVEL_NAME[mType.mLevel.ordinal()]).append(' ');
// special case: first parameter may be a quantity
if (mParameters != null && mParameters.length > 0 && mParameters[0] instanceof Integer) {
result.append(resources.getQuantityString(mType.getMsgId(), (Integer) mParameters[0], mParameters));
} else {
result.append(resources.getString(mType.getMsgId(), mParameters));
}
return result;
}
}
public static class SubLogEntryParcel extends LogEntryParcel {
@ -202,6 +226,17 @@ public abstract class OperationResult implements Parcelable {
dest.writeParcelable(mSubResult, 0);
}
@Override
StringBuilder getPrintableLogEntry(Resources resources, int indent) {
LogEntryParcel subEntry = mSubResult.getLog().getLast();
if (subEntry != null) {
return subEntry.getPrintableLogEntry(resources, mIndent +indent);
} else {
return super.getPrintableLogEntry(resources, indent);
}
}
}
public Showable createNotify(final Activity activity) {
@ -245,15 +280,15 @@ public abstract class OperationResult implements Parcelable {
}
return Notify.create(activity, logText, Notify.LENGTH_LONG, style,
new ActionListener() {
@Override
public void onAction() {
Intent intent = new Intent(
activity, LogDisplayActivity.class);
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
activity.startActivity(intent);
}
}, R.string.snackbar_details);
new ActionListener() {
@Override
public void onAction() {
Intent intent = new Intent(
activity, LogDisplayActivity.class);
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
activity.startActivity(intent);
}
}, R.string.snackbar_details);
}
@ -803,14 +838,7 @@ public abstract class OperationResult implements Parcelable {
MSG_LV_FETCH_ERROR_URL (LogLevel.ERROR, R.string.msg_lv_fetch_error_url),
MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io),
MSG_LV_FETCH_ERROR_FORMAT(LogLevel.ERROR, R.string.msg_lv_fetch_error_format),
MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing),
//export log
MSG_EXPORT_LOG(LogLevel.START,R.string.msg_export_log_start),
MSG_EXPORT_LOG_EXPORT_ERROR_NO_FILE(LogLevel.ERROR,R.string.msg_export_log_error_no_file),
MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN(LogLevel.ERROR,R.string.msg_export_log_error_fopen),
MSG_EXPORT_LOG_EXPORT_ERROR_WRITING(LogLevel.ERROR,R.string.msg_export_log_error_writing),
MSG_EXPORT_LOG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_log_success);
MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing);
public final int mMsgId;
public final LogLevel mLevel;
@ -833,6 +861,10 @@ public abstract class OperationResult implements Parcelable {
OK, // should occur once at the end of a successful operation
CANCELLED, // should occur once at the end of a cancelled operation
}
// for print of debug log. keep those in sync with above!
static final String[] LOG_LEVEL_NAME = new String[] {
"[DEBUG]", "[INFO]", "[WARN]", "[ERROR]", "[START]", "[OK]", "[CANCEL]"
};
@Override
public int describeContents() {
@ -931,6 +963,20 @@ public abstract class OperationResult implements Parcelable {
public Iterator<LogEntryParcel> iterator() {
return mParcels.iterator();
}
/**
* returns an indented String of an entire OperationLog
* @param indent padding to add at the start of all log entries, made for use with SubLogs
* @return printable, indented version of passed operationLog
*/
public String getPrintableOperationLog(Resources resources, int indent) {
StringBuilder log = new StringBuilder();
for (LogEntryParcel entry : this) {
log.append(entry.getPrintableLogEntry(resources, indent)).append("\n");
}
return log.toString().substring(0, log.length() -1); // get rid of extra new line
}
}
}

View file

@ -18,11 +18,12 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.support.v4.app.ListFragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
@ -37,19 +38,19 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import java.io.IOException;
import java.io.OutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
public class LogDisplayFragment extends ListFragment implements OnItemClickListener {
@ -60,6 +61,8 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
public static final String EXTRA_RESULT = "log";
protected int mTextColor;
private Uri mLogTempFile;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -118,170 +121,40 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_log_display_export_log:
exportLog();
shareLog();
break;
}
return super.onOptionsItemSelected(item);
}
private void exportLog() {
showExportLogDialog(new File(Constants.Path.APP_DIR, "export.log"));
}
private void shareLog() {
private void writeToLogFile(final OperationResult.OperationLog operationLog, final File f) {
OperationResult.OperationLog currLog = new OperationResult.OperationLog();
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG, 0);
boolean error = false;
PrintWriter pw = null;
try {
pw = new PrintWriter(f);
pw.print(getPrintableOperationLog(operationLog, ""));
if (pw.checkError()) {//IOException
Log.e(Constants.TAG, "Log Export I/O Exception " + f.getAbsolutePath());
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1);
error = true;
}
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "File not found for exporting log " + f.getAbsolutePath());
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN, 1);
error = true;
Activity activity = getActivity();
if (activity == null) {
return;
}
if (pw != null) {
pw.close();
if (!error && pw.checkError()) {//check if it is only pw.close() which generated error
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_ERROR_WRITING, 1);
error = true;
String log = mResult.getLog().getPrintableOperationLog(getResources(), 0);
// if there is no log temp file yet, create one
if (mLogTempFile == null) {
mLogTempFile = TemporaryStorageProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain");
try {
OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile);
outputStream.write(log.getBytes());
} catch (IOException e) {
Notify.create(activity, R.string.error_log_share_internal, Style.ERROR).show();
return;
}
}
if (!error) {
currLog.add(OperationResult.LogType.MSG_EXPORT_LOG_EXPORT_SUCCESS, 1);
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, mLogTempFile);
intent.setType("text/plain");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
int opResultCode = error ? OperationResult.RESULT_ERROR : OperationResult.RESULT_OK;
OperationResult opResult = new LogExportResult(opResultCode, currLog);
opResult.createNotify(getActivity()).show();
}
/**
* returns an indented String of an entire OperationLog
*
* @param opLog log to be converted to indented, printable format
* @param basePadding padding to add at the start of all log entries, made for use with SubLogs
* @return printable, indented version of passed operationLog
*/
private String getPrintableOperationLog(OperationResult.OperationLog opLog, String basePadding) {
String log = "";
for (LogEntryParcel anOpLog : opLog) {
log += getPrintableLogEntry(anOpLog, basePadding) + "\n";
}
log = log.substring(0, log.length() - 1);//gets rid of extra new line
return log;
}
/**
* returns an indented String of a LogEntryParcel including any sub-logs it may contain
*
* @param entryParcel log entryParcel whose String representation is to be obtained
* @return indented version of passed log entryParcel in a readable format
*/
private String getPrintableLogEntry(OperationResult.LogEntryParcel entryParcel,
String basePadding) {
final String indent = " ";//4 spaces = 1 Indent level
String padding = basePadding;
for (int i = 0; i < entryParcel.mIndent; i++) {
padding += indent;
}
String logText = padding;
switch (entryParcel.mType.mLevel) {
case DEBUG:
logText += "[DEBUG]";
break;
case INFO:
logText += "[INFO]";
break;
case WARN:
logText += "[WARN]";
break;
case ERROR:
logText += "[ERROR]";
break;
case START:
logText += "[START]";
break;
case OK:
logText += "[OK]";
break;
case CANCELLED:
logText += "[CANCELLED]";
break;
}
// special case: first parameter may be a quantity
if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
&& entryParcel.mParameters[0] instanceof Integer) {
logText += getResources().getQuantityString(entryParcel.mType.getMsgId(),
(Integer) entryParcel.mParameters[0],
entryParcel.mParameters);
} else {
logText += getResources().getString(entryParcel.mType.getMsgId(),
entryParcel.mParameters);
}
if (entryParcel instanceof SubLogEntryParcel) {
OperationResult subResult = ((SubLogEntryParcel) entryParcel).getSubResult();
LogEntryParcel subEntry = subResult.getLog().getLast();
if (subEntry != null) {
//the first line of log of subResult is same as entryParcel, so replace logText
logText = getPrintableOperationLog(subResult.getLog(), padding);
}
}
return logText;
}
private void showExportLogDialog(final File exportFile) {
String title = this.getString(R.string.title_export_log);
String message = this.getString(R.string.specify_file_to_export_log_to);
FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() {
@Override
public void onFileSelected(File file, boolean checked) {
writeToLogFile(mResult.getLog(), file);
}
}, this.getActivity().getSupportFragmentManager(), title, message, exportFile, null);
}
private static class LogExportResult extends OperationResult {
public static Creator<LogExportResult> CREATOR = new Creator<LogExportResult>() {
public LogExportResult createFromParcel(final Parcel source) {
return new LogExportResult(source);
}
public LogExportResult[] newArray(final int size) {
return new LogExportResult[size];
}
};
public LogExportResult(int result, OperationLog log) {
super(result, log);
}
/**
* trivial but necessary to implement the Parcelable protocol.
*/
public LogExportResult(Parcel source) {
super(source);
}
}
@Override

View file

@ -4,8 +4,8 @@
<item
android:id="@+id/menu_log_display_export_log"
android:icon="@drawable/ic_save_white_24dp"
android:title="@string/menu_export_log"
android:icon="@drawable/ic_share_black_24dp"
android:title="@string/menu_share_log"
app:showAsAction="ifRoom|withText" />
</menu>

View file

@ -37,7 +37,6 @@
<string name="title_exchange_keys">"Exchange Keys"</string>
<string name="title_advanced_key_info">"Extended Information"</string>
<string name="title_delete_secret_key">"Delete YOUR key '%s'?"</string>
<string name="title_export_log">"Export Log"</string>
<string name="title_manage_my_keys">"Manage my keys"</string>
<!-- section -->
@ -117,7 +116,7 @@
<string name="menu_advanced">"Extended information"</string>
<string name="menu_certify_fingerprint">"Confirm via fingerprint"</string>
<string name="menu_certify_fingerprint_word">"Confirm via words"</string>
<string name="menu_export_log">"Export Log"</string>
<string name="menu_share_log">"Share Log"</string>
<string name="menu_keyserver_add">"Add"</string>
@ -322,8 +321,6 @@
<string name="key_creation_el_gamal_info">"Note: only subkeys support ElGamal."</string>
<string name="key_not_found">"Couldn't find key %08X."</string>
<string name="specify_file_to_export_log_to">"Please specify file to export to. \nWARNING: File will be overwritten if it exists."</string>
<plurals name="bad_keys_encountered">"
<item quantity="one">"%d bad secret key ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.""</item>
<item quantity="other">"%d bad secret keys ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.""</item>
@ -1383,13 +1380,6 @@
<string name="msg_keybase_error_specific">"%s"</string>
<string name="msg_keybase_error_msg_payload_mismatch">"Decrypted proof post does not match expected value"</string>
<!-- Messages for Export Log operation -->
<string name="msg_export_log_start">"Exporting log"</string>
<string name="msg_export_log_error_fopen">"Error opening file"</string>
<string name="msg_export_log_error_no_file">"No file name specified!"</string>
<string name="msg_export_log_error_writing">"I/O error writing to file!"</string>
<string name="msg_export_log_success">"Log exported successfully!"</string>
<!-- PassphraseCache -->
<string name="passp_cache_notif_click_to_clear">"Touch to clear passwords."</string>
<plurals name="passp_cache_notif_n_keys">
@ -1452,6 +1442,7 @@
<string name="error_multi_clipboard">"Encryption of multiple files to clipboard not supported."</string>
<string name="error_detached_signature">"Sign-only operation of binary files is not supported, select at least one encryption key."</string>
<string name="error_empty_text">"Type some text to encrypt!"</string>
<string name="error_log_share_internal">"Internal error while preparing log!"</string>
<string name="key_colon">"Key:"</string>
<string name="exchange_description">"To start a key exchange, choose the number of participants on the right side, then hit the “Start exchange” button.\n\nYou will be asked two more questions to make sure only the right participants are in the exchange and their fingerprints are correct."</string>
<string name="btn_start_exchange">"Start exchange"</string>