From 9192d5c751f2aaf1d18bb9b2f715905ffd9d5925 Mon Sep 17 00:00:00 2001 From: Hans Boehm Date: Tue, 22 Sep 2015 17:09:01 -0700 Subject: Cleanup of timeout handling and message Bug: 21470513 Prevent timeout message from disappearing on rotation. Have long timeout setting survive rotation. Following Justin's suggestion, associate it with a given expression and reset when expression is cleared. This does mean that when you rotate a device displaying an expensive- -to-compute result, the device will initially display the formula for several seconds, before it redisplays the result. Previously you had to reenable the long timeout. Neither is 100% ideal. Change-Id: Ibf8e151dd37ebadf1e86adee4718e8fa8f66b975 (cherry picked from commit 5e6a0ca2fcccb9ed16a465cf2a7e30ee5f7e0e67) --- .../android/calculator2/AlertDialogFragment.java | 54 +++++++++-- src/com/android/calculator2/Calculator.java | 17 +++- src/com/android/calculator2/Evaluator.java | 103 ++++++++++++++------- 3 files changed, 131 insertions(+), 43 deletions(-) diff --git a/src/com/android/calculator2/AlertDialogFragment.java b/src/com/android/calculator2/AlertDialogFragment.java index bb7a50b..49f9549 100644 --- a/src/com/android/calculator2/AlertDialogFragment.java +++ b/src/com/android/calculator2/AlertDialogFragment.java @@ -21,22 +21,50 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; +import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.widget.TextView; -public class AlertDialogFragment extends DialogFragment { +/** + * Display a message with a dismiss putton, and optionally a second button. + */ +public class AlertDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { + + public interface OnClickListener { + /** + * This method will be invoked when a button in the dialog is clicked. + * + * @param fragment the AlertDialogFragment that received the click + * @param which the button that was clicked (e.g. + * {@link DialogInterface#BUTTON_POSITIVE}) or the position + * of the item clicked + */ + public void onClick(AlertDialogFragment fragment, int which); + } private static final String NAME = AlertDialogFragment.class.getName(); private static final String KEY_MESSAGE = NAME + "_message"; private static final String KEY_BUTTON_NEGATIVE = NAME + "_button_negative"; + private static final String KEY_BUTTON_POSITIVE = NAME + "_button_positive"; - public static void showMessageDialog(Activity activity, CharSequence message) { + /** + * Create and show a DialogFragment with the given message. + * @param activity originating Activity + * @param message displayed message + * @param positiveButtonLabel label for second button, if any. If non-null, activity must + * implement AlertDialogFragment.OnClickListener to respond. + */ + public static void showMessageDialog(Activity activity, CharSequence message, + @Nullable CharSequence positiveButtonLabel) { + final AlertDialogFragment dialogFragment = new AlertDialogFragment(); final Bundle args = new Bundle(); args.putCharSequence(KEY_MESSAGE, message); args.putCharSequence(KEY_BUTTON_NEGATIVE, activity.getString(R.string.dismiss)); - - final AlertDialogFragment dialogFragment = new AlertDialogFragment(); + if (positiveButtonLabel != null) { + args.putCharSequence(KEY_BUTTON_POSITIVE, positiveButtonLabel); + } dialogFragment.setArguments(args); dialogFragment.show(activity.getFragmentManager(), null /* tag */); } @@ -53,9 +81,21 @@ public class AlertDialogFragment extends DialogFragment { final TextView textView = (TextView) inflater.inflate(R.layout.dialog_message, null /* root */); textView.setText(args.getCharSequence(KEY_MESSAGE)); - return new AlertDialog.Builder(context) + final AlertDialog.Builder builder = new AlertDialog.Builder(context) .setView(textView) - .setNegativeButton(args.getCharSequence(KEY_BUTTON_NEGATIVE), null /* listener */) - .create(); + .setNegativeButton(args.getCharSequence(KEY_BUTTON_NEGATIVE), null /* listener */); + final CharSequence positiveButtonLabel = args.getCharSequence(KEY_BUTTON_POSITIVE); + if (positiveButtonLabel != null) { + builder.setPositiveButton(positiveButtonLabel, this); + } + return builder.create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + final Activity activity = getActivity(); + if (activity instanceof AlertDialogFragment.OnClickListener /* always true */) { + ((AlertDialogFragment.OnClickListener) activity).onClick(this, which); + } } } diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java index 99ef032..fde9d38 100644 --- a/src/com/android/calculator2/Calculator.java +++ b/src/com/android/calculator2/Calculator.java @@ -32,7 +32,9 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.app.Activity; +import android.app.AlertDialog; import android.content.ClipData; +import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; import android.graphics.Color; @@ -71,7 +73,8 @@ import java.io.ObjectOutput; import java.io.ObjectOutputStream; public class Calculator extends Activity - implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener { + implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener, + AlertDialogFragment.OnClickListener { /** * Constant for an invalid resource id. @@ -839,7 +842,15 @@ public class Calculator extends Activity mFormulaText.setTranslationY(0.0f); mFormulaText.requestFocus(); - } + } + + @Override + public void onClick(AlertDialogFragment fragment, int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + // Timeout extension request. + mEvaluator.setLongTimeOut(); + } + } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -881,7 +892,7 @@ public class Calculator extends Activity } private void displayMessage(String s) { - AlertDialogFragment.showMessageDialog(this, s); + AlertDialogFragment.showMessageDialog(this, s, null); } private void displayFraction() { diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java index fca757d..936d618 100644 --- a/src/com/android/calculator2/Evaluator.java +++ b/src/com/android/calculator2/Evaluator.java @@ -234,43 +234,65 @@ class Evaluator { .show(); } - // Maximum timeout for background computations. Exceeding a few tens of seconds - // increases the risk of running out of memory and impacting the rest of the system. - private final long MAX_TIMEOUT = 15000; + // Timeout handling. + // Expressions are evaluated with a sort timeout or a long timeout. + // Each implies different maxima on both computation time and bit length. + // We recheck bit length separetly to avoid wasting time on decimal conversions that are + // destined to fail. - // Timeout for requested evaluations, in milliseconds. This is currently not saved and - // restored with the state; we reset the timeout when the calculator is restarted. We'll call - // that a feature; others might argue it's a bug. - private long mTimeout = 2000; + /** + * Is a long timeout in effect for the main expression? + */ + private boolean mLongTimeout = false; + + /** + * Is a long timeout in effect for the saved expression? + */ + private boolean mLongSavedTimeout = false; + + /** + * Return the timeout in milliseconds. + * @param longTimeout a long timeout is in effect + */ + private long getTimeout(boolean longTimeout) { + return longTimeout ? 15000 : 2000; + // Exceeding a few tens of seconds increases the risk of running out of memory + // and impacting the rest of the system. + } - // Timeout for unrequested, speculative evaluations, in milliseconds. + /** + * Return the maximum number of bits in the result. Longer results are assumed to time out. + * @param longTimeout a long timeout is in effect + */ + private int getMaxResultBits(boolean longTimeout) { + return longTimeout ? 350000 : 120000; + } + + /** + * Timeout for unrequested, speculative evaluations, in milliseconds. + */ private final long QUICK_TIMEOUT = 1000; - private int mMaxResultBits = 120000; // Don't try to display a larger result. - private final int MAX_MAX_RESULT_BITS = 350000; // Long timeout version. - private final int QUICK_MAX_RESULT_BITS = 50000; // Instant result version. + /** + * Maximum result bit length for unrequested, speculative evaluations. + */ + private final int QUICK_MAX_RESULT_BITS = 50000; private void displayTimeoutMessage() { - final AlertDialog.Builder builder = new AlertDialog.Builder(mCalculator) - .setMessage(R.string.timeout) - .setNegativeButton(R.string.dismiss, null /* listener */); - if (mTimeout != MAX_TIMEOUT) { - builder.setPositiveButton(R.string.ok_remove_timeout, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface d, int which) { - mTimeout = MAX_TIMEOUT; - mMaxResultBits = MAX_MAX_RESULT_BITS; - } - }); - } - builder.show(); + AlertDialogFragment.showMessageDialog(mCalculator, mCalculator.getString(R.string.timeout), + (mLongTimeout ? null : mCalculator.getString(R.string.ok_remove_timeout))); } - // Compute initial cache contents and result when we're good and ready. - // We leave the expression display up, with scrolling - // disabled, until this computation completes. - // Can result in an error display if something goes wrong. - // By default we set a timeout to catch runaway computations. + public void setLongTimeOut() { + mLongTimeout = true; + } + + /** + * Compute initial cache contents and result when we're good and ready. + * We leave the expression display up, with scrolling disabled, until this computation + * completes. Can result in an error display if something goes wrong. By default we set a + * timeout to catch runaway computations. + */ class AsyncEvaluator extends AsyncTask { private boolean mDm; // degrees private boolean mRequired; // Result was requested by user. @@ -299,7 +321,7 @@ class Evaluator { } @Override protected void onPreExecute() { - long timeout = mRequired ? mTimeout : QUICK_TIMEOUT; + long timeout = mRequired ? getTimeout(mLongTimeout) : QUICK_TIMEOUT; mTimeoutRunnable = new Runnable() { @Override public void run() { @@ -312,7 +334,7 @@ class Evaluator { * Is a computed result too big for decimal conversion? */ private boolean isTooBig(CalculatorExpr.EvalResult res) { - int maxBits = mRequired ? mMaxResultBits : QUICK_MAX_RESULT_BITS; + int maxBits = mRequired ? getMaxResultBits(mLongTimeout) : QUICK_MAX_RESULT_BITS; if (res.ratVal != null) { return res.ratVal.wholeNumberBits() > maxBits; } else { @@ -821,11 +843,17 @@ class Evaluator { mMsdIndex = INVALID_MSD; } - public void clear() { + + private void clearPreservingTimeout() { mExpr.clear(); clearCache(); } + public void clear() { + clearPreservingTimeout(); + mLongTimeout = false; + } + /** * Start asynchronous result evaluation of formula. * Will result in display on completion. @@ -916,6 +944,8 @@ class Evaluator { try { CalculatorExpr.initExprInput(); mDegreeMode = in.readBoolean(); + mLongTimeout = in.readBoolean(); + mLongSavedTimeout = in.readBoolean(); mExpr = new CalculatorExpr(in); mSavedName = in.readUTF(); mSaved = new CalculatorExpr(in); @@ -931,6 +961,8 @@ class Evaluator { try { CalculatorExpr.initExprOutput(); out.writeBoolean(mDegreeMode); + out.writeBoolean(mLongTimeout); + out.writeBoolean(mLongSavedTimeout); mExpr.write(out); out.writeUTF(mSavedName); mSaved.write(out); @@ -959,6 +991,9 @@ class Evaluator { public void delete() { mChangedValue = true; mExpr.delete(); + if (mExpr.isEmpty()) { + mLongTimeout = false; + } } void setDegreeMode(boolean degreeMode) { @@ -993,7 +1028,7 @@ class Evaluator { */ public void collapse() { final CalculatorExpr abbrvExpr = getResultExpr(); - clear(); + clearPreservingTimeout(); mExpr.append(abbrvExpr); mChangedValue = true; } @@ -1009,6 +1044,7 @@ class Evaluator { final CalculatorExpr abbrvExpr = getResultExpr(); mSaved.clear(); mSaved.append(abbrvExpr); + mLongSavedTimeout = mLongTimeout; return true; } @@ -1042,6 +1078,7 @@ class Evaluator { public void appendSaved() { mChangedValue = true; + mLongTimeout |= mLongSavedTimeout; mExpr.append(mSaved); } -- cgit v1.2.3