From cdf67c3296b9a7aadf3528ed3eed29cdac752dde Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Fri, 11 Sep 2015 03:08:53 +0200 Subject: [PATCH] share rather than save log files (OKC-01-015) --- .../operations/results/OperationResult.java | 80 ++++++-- .../keychain/ui/LogDisplayFragment.java | 191 +++--------------- .../src/main/res/menu/log_display.xml | 4 +- OpenKeychain/src/main/res/values/strings.xml | 13 +- 4 files changed, 99 insertions(+), 189 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 6e9aca30d..41691933e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -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 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 + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 4de83337e..43c8d2643 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -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 CREATOR = new Creator() { - 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 diff --git a/OpenKeychain/src/main/res/menu/log_display.xml b/OpenKeychain/src/main/res/menu/log_display.xml index 7a2f77b79..a7f7e286d 100644 --- a/OpenKeychain/src/main/res/menu/log_display.xml +++ b/OpenKeychain/src/main/res/menu/log_display.xml @@ -4,8 +4,8 @@ diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index d55d128d3..151c66613 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -37,7 +37,6 @@ "Exchange Keys" "Extended Information" "Delete YOUR key '%s'?" - "Export Log" "Manage my keys" @@ -117,7 +116,7 @@ "Extended information" "Confirm via fingerprint" "Confirm via words" - "Export Log" + "Share Log" "Add" @@ -322,8 +321,6 @@ "Note: only subkeys support ElGamal." "Couldn't find key %08X." - "Please specify file to export to. \nWARNING: File will be overwritten if it exists." - " "%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."" "%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."" @@ -1383,13 +1380,6 @@ "%s" "Decrypted proof post does not match expected value" - - "Exporting log" - "Error opening file" - "No file name specified!" - "I/O error writing to file!" - "Log exported successfully!" - "Touch to clear passwords." @@ -1452,6 +1442,7 @@ "Encryption of multiple files to clipboard not supported." "Sign-only operation of binary files is not supported, select at least one encryption key." "Type some text to encrypt!" + "Internal error while preparing log!" "Key:" "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." "Start exchange"