14b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen/*
24459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen * Copyright (C) 2015 The Android Open Source Project
34b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen *
44b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * Licensed under the Apache License, Version 2.0 (the "License");
54b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * you may not use this file except in compliance with the License.
64b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * You may obtain a copy of the License at
74b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen *
84b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen *   http://www.apache.org/licenses/LICENSE-2.0
94b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen *
104b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * Unless required by applicable law or agreed to in writing, software
114b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * distributed under the License is distributed on an "AS IS" BASIS,
124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * See the License for the specific language governing permissions and
144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen * limitations under the License.
154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen */
164b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
17013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm// TODO: Copy & more general paste in formula?  Note that this requires
18013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       great care: Currently the text version of a displayed formula
19013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       is not directly useful for re-evaluating the formula later, since
20013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       it contains ellipses representing subexpressions evaluated with
21013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       a different degree mode.  Rather than supporting copy from the
22013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       formula window, we may eventually want to support generation of a
23013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       more useful text version in a separate window.  It's not clear
24013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm//       this is worth the added (code and user) complexity.
2584614957604253d51296e06c97daced699a0a9deHans Boehm
264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenpackage com.android.calculator2;
274b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
284b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.Animator;
295f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassenimport android.animation.Animator.AnimatorListener;
304b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.AnimatorListenerAdapter;
314b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.AnimatorSet;
324b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.animation.ObjectAnimator;
334459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.animation.PropertyValuesHolder;
344b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.app.Activity;
359192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehmimport android.app.AlertDialog;
36fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassenimport android.content.ClipData;
379192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehmimport android.content.DialogInterface;
38d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassenimport android.content.Intent;
39bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehmimport android.content.res.Resources;
404a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.graphics.Color;
418fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassenimport android.graphics.Rect;
424a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.net.Uri;
434b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.os.Bundle;
44f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassenimport android.support.annotation.NonNull;
453b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassenimport android.support.v4.view.ViewPager;
464a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.text.SpannableString;
478a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehmimport android.text.SpannableStringBuilder;
484a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.text.Spanned;
494a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.text.style.ForegroundColorSpan;
508a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehmimport android.text.TextUtils;
514459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassenimport android.util.Property;
524a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehmimport android.view.KeyCharacterMap;
53ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoroimport android.view.KeyEvent;
5484614957604253d51296e06c97daced699a0a9deHans Boehmimport android.view.Menu;
5584614957604253d51296e06c97daced699a0a9deHans Boehmimport android.view.MenuItem;
564b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.view.View;
57ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoroimport android.view.View.OnKeyListener;
584b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.view.View.OnLongClickListener;
595f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassenimport android.view.ViewAnimationUtils;
608fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassenimport android.view.ViewGroupOverlay;
614b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassenimport android.view.animation.AccelerateDecelerateInterpolator;
62fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassenimport android.widget.TextView;
63d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassenimport android.widget.Toolbar;
64fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
6508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehmimport com.android.calculator2.CalculatorText.OnTextSizeChangeListener;
664b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
6784614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ByteArrayInputStream;
6884614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ByteArrayOutputStream;
69721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassenimport java.io.IOException;
7084614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ObjectInput;
71721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassenimport java.io.ObjectInputStream;
7284614957604253d51296e06c97daced699a0a9deHans Boehmimport java.io.ObjectOutput;
73721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassenimport java.io.ObjectOutputStream;
742be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen
7584614957604253d51296e06c97daced699a0a9deHans Boehmpublic class Calculator extends Activity
769192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm        implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener,
779192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm        AlertDialogFragment.OnClickListener {
782be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen
792be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen    /**
802be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen     * Constant for an invalid resource id.
812be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen     */
822be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen    public static final int INVALID_RES_ID = -1;
834b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
844b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private enum CalculatorState {
8584614957604253d51296e06c97daced699a0a9deHans Boehm        INPUT,          // Result and formula both visible, no evaluation requested,
8684614957604253d51296e06c97daced699a0a9deHans Boehm                        // Though result may be visible on bottom line.
8784614957604253d51296e06c97daced699a0a9deHans Boehm        EVALUATE,       // Both visible, evaluation requested, evaluation/animation incomplete.
88c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                        // Not used for instant result evaluation.
8984614957604253d51296e06c97daced699a0a9deHans Boehm        INIT,           // Very temporary state used as alternative to EVALUATE
9084614957604253d51296e06c97daced699a0a9deHans Boehm                        // during reinitialization.  Do not animate on completion.
9184614957604253d51296e06c97daced699a0a9deHans Boehm        ANIMATE,        // Result computed, animation to enlarge result window in progress.
9284614957604253d51296e06c97daced699a0a9deHans Boehm        RESULT,         // Result displayed, formula invisible.
9384614957604253d51296e06c97daced699a0a9deHans Boehm                        // If we are in RESULT state, the formula was evaluated without
9484614957604253d51296e06c97daced699a0a9deHans Boehm                        // error to initial precision.
9584614957604253d51296e06c97daced699a0a9deHans Boehm        ERROR           // Error displayed: Formula visible, result shows error message.
9684614957604253d51296e06c97daced699a0a9deHans Boehm                        // Display similar to INPUT state.
974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
9884614957604253d51296e06c97daced699a0a9deHans Boehm    // Normal transition sequence is
9984614957604253d51296e06c97daced699a0a9deHans Boehm    // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT
10084614957604253d51296e06c97daced699a0a9deHans Boehm    // A RESULT -> ERROR transition is possible in rare corner cases, in which
10184614957604253d51296e06c97daced699a0a9deHans Boehm    // a higher precision evaluation exposes an error.  This is possible, since we
10284614957604253d51296e06c97daced699a0a9deHans Boehm    // initially evaluate assuming we were given a well-defined problem.  If we
10384614957604253d51296e06c97daced699a0a9deHans Boehm    // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0
10484614957604253d51296e06c97daced699a0a9deHans Boehm    // unless we are asked for enough precision that we can distinguish the argument from zero.
10584614957604253d51296e06c97daced699a0a9deHans Boehm    // TODO: Consider further heuristics to reduce the chance of observing this?
10684614957604253d51296e06c97daced699a0a9deHans Boehm    //       It already seems to be observable only in contrived cases.
10784614957604253d51296e06c97daced699a0a9deHans Boehm    // ANIMATE, ERROR, and RESULT are translated to an INIT state if the application
10884614957604253d51296e06c97daced699a0a9deHans Boehm    // is restarted in that state.  This leads us to recompute and redisplay the result
10984614957604253d51296e06c97daced699a0a9deHans Boehm    // ASAP.
11084614957604253d51296e06c97daced699a0a9deHans Boehm    // TODO: Possibly save a bit more information, e.g. its initial display string
11184614957604253d51296e06c97daced699a0a9deHans Boehm    // or most significant digit position, to speed up restart.
1124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
1134459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    private final Property<TextView, Integer> TEXT_COLOR =
1144459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            new Property<TextView, Integer>(Integer.class, "textColor") {
1154459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        @Override
1164459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        public Integer get(TextView textView) {
1174459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            return textView.getCurrentTextColor();
1184459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        }
1194459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen
1204459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        @Override
1214459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        public void set(TextView textView, Integer textColor) {
1224459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            textView.setTextColor(textColor);
1234459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        }
1244459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    };
1254459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen
1264a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // We currently assume that the formula does not change out from under us in
1274a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // any way. We explicitly handle all input to the formula here.
128ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro    private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
129ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        @Override
130ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
1311176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            stopActionMode();
13206c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen            // Never consume DPAD key events.
13306c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen            switch (keyCode) {
13406c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen                case KeyEvent.KEYCODE_DPAD_UP:
13506c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen                case KeyEvent.KEYCODE_DPAD_DOWN:
13606c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen                case KeyEvent.KEYCODE_DPAD_LEFT:
13706c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen                case KeyEvent.KEYCODE_DPAD_RIGHT:
13806c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen                    return false;
13906c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen            }
140c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            // Always cancel unrequested in-progress evaluation, so that we don't have
141c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            // to worry about subsequent asynchronous completion.
142c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            // Requested in-progress evaluations are handled below.
143c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            if (mCurrentState != CalculatorState.EVALUATE) {
144c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                mEvaluator.cancelAll(true);
145c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            }
146c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            // In other cases we go ahead and process the input normally after cancelling:
14706c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen            if (keyEvent.getAction() != KeyEvent.ACTION_UP) {
14806c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen                return true;
14906c4944949254d1d34d6b6dde44f289c332156daJustin Klaassen            }
150ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro            switch (keyCode) {
151ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro                case KeyEvent.KEYCODE_NUMPAD_ENTER:
152ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro                case KeyEvent.KEYCODE_ENTER:
1534a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                case KeyEvent.KEYCODE_DPAD_CENTER:
1544a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    mCurrentButton = mEqualButton;
1554a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    onEquals();
1564a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    return true;
1574a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                case KeyEvent.KEYCODE_DEL:
1584a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    mCurrentButton = mDeleteButton;
1594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    onDelete();
1604a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    return true;
1614a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                default:
162c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                    cancelIfEvaluating(false);
1634a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    final int raw = keyEvent.getKeyCharacterMap()
1644459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                            .get(keyCode, keyEvent.getMetaState());
1654a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
1664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                        return true; // discard
1674a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    }
1684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    // Try to discard non-printing characters and the like.
1694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    // The user will have to explicitly delete other junk that gets past us.
1704a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    if (Character.isIdentifierIgnorable(raw)
1714459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                            || Character.isWhitespace(raw)) {
1724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                        return true;
1734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    }
1744459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    char c = (char) raw;
1754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    if (c == '=') {
176e57fb01d48dbb06b4ee9c331a108212cd21a1c39Hans Boehm                        mCurrentButton = mEqualButton;
177ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro                        onEquals();
1784a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    } else {
179017de9893efd33e179db10bc71189e150bc3486dHans Boehm                        addChars(String.valueOf(c), true);
1804a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                        redisplayAfterFormulaChange();
181ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro                    }
182ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro            }
183ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro            return false;
184ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        }
185ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro    };
186ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro
18784614957604253d51296e06c97daced699a0a9deHans Boehm    private static final String NAME = Calculator.class.getName();
18884614957604253d51296e06c97daced699a0a9deHans Boehm    private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
189760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm    private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
19084614957604253d51296e06c97daced699a0a9deHans Boehm    private static final String KEY_EVAL_STATE = NAME + "_eval_state";
19184614957604253d51296e06c97daced699a0a9deHans Boehm                // Associated value is a byte array holding both mCalculatorState
19284614957604253d51296e06c97daced699a0a9deHans Boehm                // and the (much more complex) evaluator state.
193741471e3e99760acd44f8536534ac6121af9b03fJustin Klaassen
1944b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private CalculatorState mCurrentState;
19584614957604253d51296e06c97daced699a0a9deHans Boehm    private Evaluator mEvaluator;
1964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
19706360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen    private View mDisplayView;
198d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    private TextView mModeView;
19908e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm    private CalculatorText mFormulaText;
2004459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    private CalculatorResult mResultText;
201d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen
2023b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen    private ViewPager mPadViewPager;
2034b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private View mDeleteButton;
2044b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private View mClearButton;
205d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    private View mEqualButton;
206e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
207e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    private TextView mInverseToggle;
208e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    private TextView mModeToggle;
209e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
210721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen    private View[] mInvertibleButtons;
211e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    private View[] mInverseButtons;
2124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
2135f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen    private View mCurrentButton;
2144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private Animator mCurrentAnimator;
2154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
2168a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm    // Characters that were recently entered at the end of the display that have not yet
2178a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm    // been added to the underlying expression.
2188a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm    private String mUnprocessedChars = null;
2198a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm
2208a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm    // Color to highlight unprocessed characters from physical keyboard.
2218a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm    // TODO: should probably match this to the error color?
2228a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm    private ForegroundColorSpan mUnprocessedColorSpan = new ForegroundColorSpan(Color.RED);
2234a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
2244b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    @Override
2254b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    protected void onCreate(Bundle savedInstanceState) {
2264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        super.onCreate(savedInstanceState);
2275f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        setContentView(R.layout.activity_calculator);
228d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        setActionBar((Toolbar) findViewById(R.id.toolbar));
229d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen
230d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        // Hide all default options in the ActionBar.
231d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        getActionBar().setDisplayOptions(0);
2324b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
23306360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen        mDisplayView = findViewById(R.id.display);
234e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        mModeView = (TextView) findViewById(R.id.mode);
23508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        mFormulaText = (CalculatorText) findViewById(R.id.formula);
2364459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText = (CalculatorResult) findViewById(R.id.result);
237d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen
2383b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen        mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
2394b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mDeleteButton = findViewById(R.id.del);
2404b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mClearButton = findViewById(R.id.clr);
241ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
242ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
243ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro            mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
244ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        }
245e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
246e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        mInverseToggle = (TextView) findViewById(R.id.toggle_inv);
247e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        mModeToggle = (TextView) findViewById(R.id.toggle_mode);
248e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
249721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen        mInvertibleButtons = new View[] {
250721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen                findViewById(R.id.fun_sin),
251721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen                findViewById(R.id.fun_cos),
2524db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.fun_tan),
2534db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.fun_ln),
2544db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.fun_log),
2554db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.op_sqrt)
256e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        };
257e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        mInverseButtons = new View[] {
258e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                findViewById(R.id.fun_arcsin),
259e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                findViewById(R.id.fun_arccos),
2604db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.fun_arctan),
2614db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.fun_exp),
2624db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.fun_10pow),
2634db31b490443e4454d98a5ae2bc44b87149accfeHans Boehm                findViewById(R.id.op_sqr)
264e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        };
26584614957604253d51296e06c97daced699a0a9deHans Boehm
2664459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mEvaluator = new Evaluator(this, mResultText);
2674459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setEvaluator(mEvaluator);
268013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        KeyMaps.setActivity(this);
26984614957604253d51296e06c97daced699a0a9deHans Boehm
27084614957604253d51296e06c97daced699a0a9deHans Boehm        if (savedInstanceState != null) {
27184614957604253d51296e06c97daced699a0a9deHans Boehm            setState(CalculatorState.values()[
27284614957604253d51296e06c97daced699a0a9deHans Boehm                savedInstanceState.getInt(KEY_DISPLAY_STATE,
27384614957604253d51296e06c97daced699a0a9deHans Boehm                                          CalculatorState.INPUT.ordinal())]);
274760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm            CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
275760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm            if (unprocessed != null) {
276760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm                mUnprocessedChars = unprocessed.toString();
277760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm            }
278760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm            byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE);
27984614957604253d51296e06c97daced699a0a9deHans Boehm            if (state != null) {
28084614957604253d51296e06c97daced699a0a9deHans Boehm                try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
28184614957604253d51296e06c97daced699a0a9deHans Boehm                    mEvaluator.restoreInstanceState(in);
28284614957604253d51296e06c97daced699a0a9deHans Boehm                } catch (Throwable ignored) {
28384614957604253d51296e06c97daced699a0a9deHans Boehm                    // When in doubt, revert to clean state
28484614957604253d51296e06c97daced699a0a9deHans Boehm                    mCurrentState = CalculatorState.INPUT;
28584614957604253d51296e06c97daced699a0a9deHans Boehm                    mEvaluator.clear();
28684614957604253d51296e06c97daced699a0a9deHans Boehm                }
28784614957604253d51296e06c97daced699a0a9deHans Boehm            }
288fbcef7005de4436682072927f83000b502928d25Hans Boehm        } else {
289fbcef7005de4436682072927f83000b502928d25Hans Boehm            mCurrentState = CalculatorState.INPUT;
290fbcef7005de4436682072927f83000b502928d25Hans Boehm            mEvaluator.clear();
29184614957604253d51296e06c97daced699a0a9deHans Boehm        }
292e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
29308e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        mFormulaText.setOnKeyListener(mFormulaOnKeyListener);
29408e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        mFormulaText.setOnTextSizeChangeListener(this);
295fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        mFormulaText.setOnPasteListener(this);
2964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mDeleteButton.setOnLongClickListener(this);
297e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
298e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        onInverseToggled(mInverseToggle.isSelected());
299e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        onModeChanged(mEvaluator.getDegreeMode());
300e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
30184614957604253d51296e06c97daced699a0a9deHans Boehm        if (mCurrentState != CalculatorState.INPUT) {
302fbcef7005de4436682072927f83000b502928d25Hans Boehm            // Just reevaluate.
303fbcef7005de4436682072927f83000b502928d25Hans Boehm            redisplayFormula();
30484614957604253d51296e06c97daced699a0a9deHans Boehm            setState(CalculatorState.INIT);
30584614957604253d51296e06c97daced699a0a9deHans Boehm            mEvaluator.requireResult();
30684614957604253d51296e06c97daced699a0a9deHans Boehm        } else {
3074a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            redisplayAfterFormulaChange();
30884614957604253d51296e06c97daced699a0a9deHans Boehm        }
30984614957604253d51296e06c97daced699a0a9deHans Boehm        // TODO: We're currently not saving and restoring scroll position.
31084614957604253d51296e06c97daced699a0a9deHans Boehm        //       We probably should.  Details may require care to deal with:
31184614957604253d51296e06c97daced699a0a9deHans Boehm        //         - new display size
31284614957604253d51296e06c97daced699a0a9deHans Boehm        //         - slow recomputation if we've scrolled far.
3134b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
3144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
3154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    @Override
316f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen    protected void onSaveInstanceState(@NonNull Bundle outState) {
317f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen        // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
318f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen        if (mCurrentAnimator != null) {
319f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen            mCurrentAnimator.cancel();
320f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen        }
321f79d6f699b04a735e1627b47a059760ff40c26b9Justin Klaassen
3224b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        super.onSaveInstanceState(outState);
32384614957604253d51296e06c97daced699a0a9deHans Boehm        outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
324760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm        outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
32584614957604253d51296e06c97daced699a0a9deHans Boehm        ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
32684614957604253d51296e06c97daced699a0a9deHans Boehm        try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
32784614957604253d51296e06c97daced699a0a9deHans Boehm            mEvaluator.saveInstanceState(out);
32884614957604253d51296e06c97daced699a0a9deHans Boehm        } catch (IOException e) {
32984614957604253d51296e06c97daced699a0a9deHans Boehm            // Impossible; No IO involved.
33084614957604253d51296e06c97daced699a0a9deHans Boehm            throw new AssertionError("Impossible IO exception", e);
33184614957604253d51296e06c97daced699a0a9deHans Boehm        }
33284614957604253d51296e06c97daced699a0a9deHans Boehm        outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
3334b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
3344b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
3354a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // Set the state, updating delete label and display colors.
3364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // This restores display positions on moving to INPUT.
337d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    // But movement/animation for moving to RESULT has already been done.
3384b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    private void setState(CalculatorState state) {
3394b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        if (mCurrentState != state) {
34084614957604253d51296e06c97daced699a0a9deHans Boehm            if (state == CalculatorState.INPUT) {
34184614957604253d51296e06c97daced699a0a9deHans Boehm                restoreDisplayPositions();
34284614957604253d51296e06c97daced699a0a9deHans Boehm            }
3434b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            mCurrentState = state;
3444b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
3454a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            if (mCurrentState == CalculatorState.RESULT) {
3464a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                // No longer do this for ERROR; allow mistakes to be corrected.
3474b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                mDeleteButton.setVisibility(View.GONE);
3484b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                mClearButton.setVisibility(View.VISIBLE);
3494b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            } else {
3504b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                mDeleteButton.setVisibility(View.VISIBLE);
3514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                mClearButton.setVisibility(View.GONE);
3524b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            }
3534b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
35484614957604253d51296e06c97daced699a0a9deHans Boehm            if (mCurrentState == CalculatorState.ERROR) {
3554459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                final int errorColor = getColor(R.color.calculator_error_color);
35608e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm                mFormulaText.setTextColor(errorColor);
3574459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                mResultText.setTextColor(errorColor);
3588fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen                getWindow().setStatusBarColor(errorColor);
3594459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            } else if (mCurrentState != CalculatorState.RESULT) {
3604459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                mFormulaText.setTextColor(getColor(R.color.display_formula_text_color));
3614459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                mResultText.setTextColor(getColor(R.color.display_result_text_color));
3624459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                getWindow().setStatusBarColor(getColor(R.color.calculator_accent_color));
3634b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            }
364d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen
365d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen            invalidateOptionsMenu();
3664b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
3674b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
3684b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
3691176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    // Stop any active ActionMode.  Return true if there was one.
3701176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    private boolean stopActionMode() {
3714459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        if (mResultText.stopActionMode()) {
3721176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            return true;
3731176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        }
3741176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        if (mFormulaText.stopActionMode()) {
3751176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            return true;
3761176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        }
3771176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        return false;
3781176f23dae4d3740782e46463003e9f36a381c9dHans Boehm    }
3791176f23dae4d3740782e46463003e9f36a381c9dHans Boehm
3804b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    @Override
3813b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen    public void onBackPressed() {
3821176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        if (!stopActionMode()) {
3831176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
3841176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                // Select the previous pad.
3851176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
3861176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            } else {
3871176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                // If the user is currently looking at the first pad (or the pad is not paged),
3881176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                // allow the system to handle the Back button.
3891176f23dae4d3740782e46463003e9f36a381c9dHans Boehm                super.onBackPressed();
3901176f23dae4d3740782e46463003e9f36a381c9dHans Boehm            }
3913b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen        }
3923b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen    }
3933b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen
3943b4d13d528a14a0f46e7ae022357dfde90a307adJustin Klaassen    @Override
3954b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public void onUserInteraction() {
3964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        super.onUserInteraction();
3974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
398c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        // If there's an animation in progress, end it immediately, so the user interaction can
399c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        // be handled.
4004b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        if (mCurrentAnimator != null) {
401c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            mCurrentAnimator.end();
4024b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
4034b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
4044b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
405e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    /**
406e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     * Invoked whenever the inverse button is toggled to update the UI.
407e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     *
408e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     * @param showInverse {@code true} if inverse functions should be shown
409e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     */
410e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    private void onInverseToggled(boolean showInverse) {
411e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        if (showInverse) {
412e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mInverseToggle.setContentDescription(getString(R.string.desc_inv_on));
413721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen            for (View invertibleButton : mInvertibleButtons) {
414721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen                invertibleButton.setVisibility(View.GONE);
415e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            }
416e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            for (View inverseButton : mInverseButtons) {
417e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                inverseButton.setVisibility(View.VISIBLE);
418e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            }
419e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        } else {
420e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mInverseToggle.setContentDescription(getString(R.string.desc_inv_off));
421721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen            for (View invertibleButton : mInvertibleButtons) {
422721ec84263a26b859c57eb9fb4eb66939fe94272Justin Klaassen                invertibleButton.setVisibility(View.VISIBLE);
423e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            }
424e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            for (View inverseButton : mInverseButtons) {
425e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                inverseButton.setVisibility(View.GONE);
426e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            }
427e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        }
428e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    }
429e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
430e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    /**
431e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     * Invoked whenever the deg/rad mode may have changed to update the UI.
432e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     *
433e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     * @param degreeMode {@code true} if in degree mode
434e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen     */
435e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen    private void onModeChanged(boolean degreeMode) {
436e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen        if (degreeMode) {
437d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen            mModeView.setText(R.string.mode_deg);
438e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mModeView.setContentDescription(getString(R.string.desc_mode_deg));
439e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
440e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mModeToggle.setText(R.string.mode_rad);
441e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mModeToggle.setContentDescription(getString(R.string.desc_switch_rad));
442bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm        } else {
443d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen            mModeView.setText(R.string.mode_rad);
444e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mModeView.setContentDescription(getString(R.string.desc_mode_rad));
445e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen
446e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mModeToggle.setText(R.string.mode_deg);
447e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            mModeToggle.setContentDescription(getString(R.string.desc_switch_deg));
448bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm        }
449bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm    }
45084614957604253d51296e06c97daced699a0a9deHans Boehm
451f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm    /**
452f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm     * Switch to INPUT from RESULT state in response to input of the specified button_id.
453f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm     * View.NO_ID is treated as an incomplete function id.
454f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm     */
455f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm    private void switchToInput(int button_id) {
456f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm        if (KeyMaps.isBinary(button_id) || KeyMaps.isSuffix(button_id)) {
457f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm            mEvaluator.collapse();
458f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm        } else {
459f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm            announceClearedForAccessibility();
460f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm            mEvaluator.clear();
461f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm        }
462f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm        setState(CalculatorState.INPUT);
463f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm    }
464f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm
4654a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // Add the given button id to input expression.
4664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // If appropriate, clear the expression before doing so.
4674a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void addKeyToExpr(int id) {
4684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        if (mCurrentState == CalculatorState.ERROR) {
4694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            setState(CalculatorState.INPUT);
4704a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        } else if (mCurrentState == CalculatorState.RESULT) {
471f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm            switchToInput(id);
4724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
4734a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        if (!mEvaluator.append(id)) {
4744a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            // TODO: Some user visible feedback?
4754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
4764a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
4774a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
478017de9893efd33e179db10bc71189e150bc3486dHans Boehm    /**
479017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * Add the given button id to input expression, assuming it was explicitly
480017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * typed/touched.
481017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * We perform slightly more aggressive correction than in pasted expressions.
482017de9893efd33e179db10bc71189e150bc3486dHans Boehm     */
483017de9893efd33e179db10bc71189e150bc3486dHans Boehm    private void addExplicitKeyToExpr(int id) {
484017de9893efd33e179db10bc71189e150bc3486dHans Boehm        if (mCurrentState == CalculatorState.INPUT && id == R.id.op_sub) {
485017de9893efd33e179db10bc71189e150bc3486dHans Boehm            mEvaluator.getExpr().removeTrailingAdditiveOperators();
486017de9893efd33e179db10bc71189e150bc3486dHans Boehm        }
487017de9893efd33e179db10bc71189e150bc3486dHans Boehm        addKeyToExpr(id);
488017de9893efd33e179db10bc71189e150bc3486dHans Boehm    }
489017de9893efd33e179db10bc71189e150bc3486dHans Boehm
4904a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void redisplayAfterFormulaChange() {
4914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // TODO: Could do this more incrementally.
4924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        redisplayFormula();
4934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        setState(CalculatorState.INPUT);
494c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm        if (mEvaluator.getExpr().hasInterestingOps()) {
495c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm            mEvaluator.evaluateAndShowResult();
496c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm        } else {
4974459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.clear();
498c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm        }
4994a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
5004a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
5014b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public void onButtonClick(View view) {
502c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        // Any animation is ended before we get here.
5035f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        mCurrentButton = view;
5041176f23dae4d3740782e46463003e9f36a381c9dHans Boehm        stopActionMode();
505c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        // See onKey above for the rationale behind some of the behavior below:
506c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        if (mCurrentState != CalculatorState.EVALUATE) {
507c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            // Cancel evaluations that were not specifically requested.
508c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            mEvaluator.cancelAll(true);
50984614957604253d51296e06c97daced699a0a9deHans Boehm        }
510d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        final int id = view.getId();
51184614957604253d51296e06c97daced699a0a9deHans Boehm        switch (id) {
5124b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            case R.id.eq:
513ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro                onEquals();
5144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                break;
5154b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            case R.id.del:
516ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro                onDelete();
5174b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                break;
5184b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            case R.id.clr:
5195f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen                onClear();
5204b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                break;
521e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            case R.id.toggle_inv:
522e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                final boolean selected = !mInverseToggle.isSelected();
523e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                mInverseToggle.setSelected(selected);
524e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                onInverseToggled(selected);
525c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                if (mCurrentState == CalculatorState.RESULT) {
526c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                    mResultText.redisplay();   // In case we cancelled reevaluation.
527c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                }
528e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                break;
529e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen            case R.id.toggle_mode:
530c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                cancelIfEvaluating(false);
531e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                final boolean mode = !mEvaluator.getDegreeMode();
532bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                if (mCurrentState == CalculatorState.RESULT) {
533bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                    mEvaluator.collapse();  // Capture result evaluated in old mode
534bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                    redisplayFormula();
535bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                }
536bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                // In input mode, we reinterpret already entered trig functions.
537bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                mEvaluator.setDegreeMode(mode);
538e2711cbb1569ab6c7a7c4506505ec403286d5ab4Justin Klaassen                onModeChanged(mode);
539bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                setState(CalculatorState.INPUT);
5404459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                mResultText.clear();
541c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm                if (mEvaluator.getExpr().hasInterestingOps()) {
542c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm                    mEvaluator.evaluateAndShowResult();
543c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm                }
544bfe8c22fd15ff965ed97c7245b49565c645c2ceeHans Boehm                break;
5454b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            default:
546c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                cancelIfEvaluating(false);
547017de9893efd33e179db10bc71189e150bc3486dHans Boehm                addExplicitKeyToExpr(id);
5484a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                redisplayAfterFormulaChange();
5494b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                break;
5504b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
5514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
5524b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
55384614957604253d51296e06c97daced699a0a9deHans Boehm    void redisplayFormula() {
5548a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm        SpannableStringBuilder formula = mEvaluator.getExpr().toSpannableStringBuilder(this);
5554a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        if (mUnprocessedChars != null) {
5564a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            // Add and highlight characters we couldn't process.
5578a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm            formula.append(mUnprocessedChars, mUnprocessedColorSpan,
5588a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
5594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
5608a4f81c5b30edd4e62d222a17f4e0e2140bfd99dHans Boehm        mFormulaText.changeTextTo(formula);
56184614957604253d51296e06c97daced699a0a9deHans Boehm    }
56284614957604253d51296e06c97daced699a0a9deHans Boehm
5634b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    @Override
5644b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    public boolean onLongClick(View view) {
5655f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        mCurrentButton = view;
5665f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen
5674b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        if (view.getId() == R.id.del) {
5685f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen            onClear();
5694b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            return true;
5704b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
5714b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        return false;
5724b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
5734b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
57484614957604253d51296e06c97daced699a0a9deHans Boehm    // Initial evaluation completed successfully.  Initiate display.
575a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm    public void onEvaluate(int initDisplayPrec, int msd, int leastDigPos,
576a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm            String truncatedWholeNumber) {
577d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        // Invalidate any options that may depend on the current result.
578d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        invalidateOptionsMenu();
579d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen
580a0e45f306463394d9eeeb887b42ae18c72d69136Hans Boehm        mResultText.displayResult(initDisplayPrec, msd, leastDigPos, truncatedWholeNumber);
58161568a15c8d88d86aba14a7800d0bfb46f22c8baHans Boehm        if (mCurrentState != CalculatorState.INPUT) { // in EVALUATE or INIT state
58284614957604253d51296e06c97daced699a0a9deHans Boehm            onResult(mCurrentState != CalculatorState.INIT);
5834b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        }
58484614957604253d51296e06c97daced699a0a9deHans Boehm    }
585ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro
586c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm    // Reset state to reflect evaluator cancellation.  Invoked by evaluator.
58784614957604253d51296e06c97daced699a0a9deHans Boehm    public void onCancelled() {
58884614957604253d51296e06c97daced699a0a9deHans Boehm        // We should be in EVALUATE state.
58984614957604253d51296e06c97daced699a0a9deHans Boehm        setState(CalculatorState.INPUT);
5904459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.clear();
59184614957604253d51296e06c97daced699a0a9deHans Boehm    }
59284614957604253d51296e06c97daced699a0a9deHans Boehm
59384614957604253d51296e06c97daced699a0a9deHans Boehm    // Reevaluation completed; ask result to redisplay current value.
59484614957604253d51296e06c97daced699a0a9deHans Boehm    public void onReevaluate()
59584614957604253d51296e06c97daced699a0a9deHans Boehm    {
5964459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.redisplay();
5974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
5984b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
599fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    @Override
600fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    public void onTextSizeChanged(final TextView textView, float oldSize) {
601fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        if (mCurrentState != CalculatorState.INPUT) {
602fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen            // Only animate text changes that occur from user input.
603fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen            return;
604fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        }
605fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
606fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        // Calculate the values needed to perform the scale and translation animations,
607fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        // maintaining the same apparent baseline for the displayed text.
608fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        final float textScale = oldSize / textView.getTextSize();
609fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        final float translationX = (1.0f - textScale) *
610fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen                (textView.getWidth() / 2.0f - textView.getPaddingEnd());
611fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        final float translationY = (1.0f - textScale) *
612fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen                (textView.getHeight() / 2.0f - textView.getPaddingBottom());
613fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
614fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        final AnimatorSet animatorSet = new AnimatorSet();
615fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        animatorSet.playTogether(
616fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen                ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
617fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen                ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
618fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen                ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
619fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen                ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
62094db720f7daf99923cc8e3d5ba8765b5529913f1Justin Klaassen        animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
621fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
622fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen        animatorSet.start();
623fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen    }
624fed941a1b4b2a3a011b8a17f486060f593db2f3cJustin Klaassen
625c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm    /**
626c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm     * Cancel any in-progress explicitly requested evaluations.
627c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm     * @param quiet suppress pop-up message.  Explicit evaluation can change the expression
628c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm                    value, and certainly changes the display, so it seems reasonable to warn.
629c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm     * @return      true if there was such an evaluation
630c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm     */
631c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm    private boolean cancelIfEvaluating(boolean quiet) {
632c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        if (mCurrentState == CalculatorState.EVALUATE) {
633c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            mEvaluator.cancelAll(quiet);
634c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            return true;
635c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        } else {
636c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            return false;
637c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        }
638c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm    }
639c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm
640ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro    private void onEquals() {
641c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        // In non-INPUT state assume this was redundant and ignore it.
642c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm        if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) {
643ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro            setState(CalculatorState.EVALUATE);
64484614957604253d51296e06c97daced699a0a9deHans Boehm            mEvaluator.requireResult();
645ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro        }
646ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro    }
647ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro
648ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro    private void onDelete() {
6494a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // Delete works like backspace; remove the last character or operator from the expression.
6504a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // Note that we handle keyboard delete exactly like the delete button.  For
6514a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // example the delete button can be used to delete a character from an incomplete
6524a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // function name typed on a physical keyboard.
6534a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        // This should be impossible in RESULT state.
654c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        // If there is an in-progress explicit evaluation, just cancel it and return.
655c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        if (cancelIfEvaluating(false)) return;
6564a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        setState(CalculatorState.INPUT);
6574a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        if (mUnprocessedChars != null) {
6584a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            int len = mUnprocessedChars.length();
6594a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            if (len > 0) {
6604a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
6614a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            } else {
662c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm                mEvaluator.delete();
6634a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            }
6644a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        } else {
665c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm            mEvaluator.delete();
6664a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
6674d63cfce55fa71eb208467310277bebc828be07dHans Boehm        if (mEvaluator.getExpr().isEmpty()
6684d63cfce55fa71eb208467310277bebc828be07dHans Boehm                && (mUnprocessedChars == null || mUnprocessedChars.isEmpty())) {
6694d63cfce55fa71eb208467310277bebc828be07dHans Boehm            // Resulting formula won't be announced, since it's empty.
6704d63cfce55fa71eb208467310277bebc828be07dHans Boehm            announceClearedForAccessibility();
6714d63cfce55fa71eb208467310277bebc828be07dHans Boehm        }
6724a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        redisplayAfterFormulaChange();
673ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro    }
674ad8e88a5d28ba7f5b8dfa63f92b00b3b96ada360Budi Kusmiantoro
6755f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen    private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
67606360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen        final ViewGroupOverlay groupOverlay =
67706360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen                (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
6788fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen
6798fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen        final Rect displayRect = new Rect();
68006360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen        mDisplayView.getGlobalVisibleRect(displayRect);
6815f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen
6825f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        // Make reveal cover the display and status bar.
6835f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final View revealView = new View(this);
6848fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen        revealView.setBottom(displayRect.bottom);
6858fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen        revealView.setLeft(displayRect.left);
6868fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen        revealView.setRight(displayRect.right);
6875f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        revealView.setBackgroundColor(getResources().getColor(colorRes));
68806360f9211fc2c6df4c5749bebb65202e1bb12a8Justin Klaassen        groupOverlay.add(revealView);
6895f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen
6904b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        final int[] clearLocation = new int[2];
6914b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        sourceView.getLocationInWindow(clearLocation);
6924b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        clearLocation[0] += sourceView.getWidth() / 2;
6934b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        clearLocation[1] += sourceView.getHeight() / 2;
6944b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
6955f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final int revealCenterX = clearLocation[0] - revealView.getLeft();
6965f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final int revealCenterY = clearLocation[1] - revealView.getTop();
6974b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
6985f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
6995f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
7005f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
7014b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
7024b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
7035f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final Animator revealAnimator =
7045f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen                ViewAnimationUtils.createCircularReveal(revealView,
7053d6ecaf4d3365eeaff75a4bedb57fbe136cf5f64ztenghui                        revealCenterX, revealCenterY, 0.0f, revealRadius);
7065f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        revealAnimator.setDuration(
7074b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                getResources().getInteger(android.R.integer.config_longAnimTime));
7085f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        revealAnimator.addListener(listener);
7094b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
7105f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
7114b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        alphaAnimator.setDuration(
7125f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen                getResources().getInteger(android.R.integer.config_mediumAnimTime));
7134b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
7144b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        final AnimatorSet animatorSet = new AnimatorSet();
7155f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        animatorSet.play(revealAnimator).before(alphaAnimator);
7164b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
7174b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        animatorSet.addListener(new AnimatorListenerAdapter() {
7184b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            @Override
7194b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            public void onAnimationEnd(Animator animator) {
7208fff144d6cf5d44b2a8e00b3468334019aa4f8b8Justin Klaassen                groupOverlay.remove(revealView);
7214b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen                mCurrentAnimator = null;
7224b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            }
7234b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        });
7244b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
7254b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        mCurrentAnimator = animatorSet;
7264b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen        animatorSet.start();
7274b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
7284b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
7294d63cfce55fa71eb208467310277bebc828be07dHans Boehm    private void announceClearedForAccessibility() {
7304d63cfce55fa71eb208467310277bebc828be07dHans Boehm        mResultText.announceForAccessibility(getResources().getString(R.string.cleared));
731ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm    }
732ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm
7335f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen    private void onClear() {
73484614957604253d51296e06c97daced699a0a9deHans Boehm        if (mEvaluator.getExpr().isEmpty()) {
7355f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen            return;
7365f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        }
737c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm        cancelIfEvaluating(true);
7384d63cfce55fa71eb208467310277bebc828be07dHans Boehm        announceClearedForAccessibility();
7395f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
7405f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen            @Override
7415f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen            public void onAnimationEnd(Animator animation) {
742760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm                mUnprocessedChars = null;
7434459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                mResultText.clear();
744760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm                mEvaluator.clear();
745760a9dc6573e35bcbf9097dece06cd90c4abb551Hans Boehm                setState(CalculatorState.INPUT);
74684614957604253d51296e06c97daced699a0a9deHans Boehm                redisplayFormula();
7475f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen            }
7485f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen        });
7495f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen    }
7505f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen
75184614957604253d51296e06c97daced699a0a9deHans Boehm    // Evaluation encountered en error.  Display the error.
75284614957604253d51296e06c97daced699a0a9deHans Boehm    void onError(final int errorResourceId) {
753fbcef7005de4436682072927f83000b502928d25Hans Boehm        if (mCurrentState == CalculatorState.EVALUATE) {
754fbcef7005de4436682072927f83000b502928d25Hans Boehm            setState(CalculatorState.ANIMATE);
755ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm            mResultText.announceForAccessibility(getResources().getString(errorResourceId));
756fbcef7005de4436682072927f83000b502928d25Hans Boehm            reveal(mCurrentButton, R.color.calculator_error_color,
757fbcef7005de4436682072927f83000b502928d25Hans Boehm                    new AnimatorListenerAdapter() {
758fbcef7005de4436682072927f83000b502928d25Hans Boehm                        @Override
759fbcef7005de4436682072927f83000b502928d25Hans Boehm                        public void onAnimationEnd(Animator animation) {
760fbcef7005de4436682072927f83000b502928d25Hans Boehm                           setState(CalculatorState.ERROR);
7614459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                           mResultText.displayError(errorResourceId);
762fbcef7005de4436682072927f83000b502928d25Hans Boehm                        }
763fbcef7005de4436682072927f83000b502928d25Hans Boehm                    });
764fbcef7005de4436682072927f83000b502928d25Hans Boehm        } else if (mCurrentState == CalculatorState.INIT) {
765fbcef7005de4436682072927f83000b502928d25Hans Boehm            setState(CalculatorState.ERROR);
7664459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.displayError(errorResourceId);
767c023b734310f231244f17189788ae9c17c90b9a8Hans Boehm        } else {
7684459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.clear();
7692be4fdbfcd5f16bc12066d1ccac181bca3dfaa7aJustin Klaassen        }
7705f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen    }
7715f2a334a7789cd88e210451ff5cd39bfacfe054eJustin Klaassen
77284614957604253d51296e06c97daced699a0a9deHans Boehm
77384614957604253d51296e06c97daced699a0a9deHans Boehm    // Animate movement of result into the top formula slot.
77484614957604253d51296e06c97daced699a0a9deHans Boehm    // Result window now remains translated in the top slot while the result is displayed.
77584614957604253d51296e06c97daced699a0a9deHans Boehm    // (We convert it back to formula use only when the user provides new input.)
7764459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen    // Historical note: In the Lollipop version, this invisibly and instantaneously moved
77784614957604253d51296e06c97daced699a0a9deHans Boehm    // formula and result displays back at the end of the animation.  We no longer do that,
77884614957604253d51296e06c97daced699a0a9deHans Boehm    // so that we can continue to properly support scrolling of the result.
77984614957604253d51296e06c97daced699a0a9deHans Boehm    // We assume the result already contains the text to be expanded.
78084614957604253d51296e06c97daced699a0a9deHans Boehm    private void onResult(boolean animate) {
7814459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // Calculate the textSize that would be used to display the result in the formula.
7824459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // For scrollable results just use the minimum textSize to maximize the number of digits
7834459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // that are visible on screen.
7844459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        float textSize = mFormulaText.getMinimumTextSize();
7854459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        if (!mResultText.isScrollable()) {
7864459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            textSize = mFormulaText.getVariableTextSize(mResultText.getText().toString());
7874459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        }
7884459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen
7894459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // Scale the result to match the calculated textSize, minimizing the jump-cut transition
7904459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // when a result is reused in a subsequent expression.
7914459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        final float resultScale = textSize / mResultText.getTextSize();
7924459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen
7934459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // Set the result's pivot to match its gravity.
7944459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setPivotX(mResultText.getWidth() - mResultText.getPaddingRight());
7954459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setPivotY(mResultText.getHeight() - mResultText.getPaddingBottom());
7964459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen
7974459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // Calculate the necessary translations so the result takes the place of the formula and
7984459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // the formula moves off the top of the screen.
7994459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        final float resultTranslationY = (mFormulaText.getBottom() - mResultText.getBottom())
8004459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom());
80108e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        final float formulaTranslationY = -mFormulaText.getBottom();
8024b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
8034459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        // Change the result's textColor to match the formula.
8044459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        final int formulaTextColor = mFormulaText.getCurrentTextColor();
8054459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen
80684614957604253d51296e06c97daced699a0a9deHans Boehm        if (animate) {
807ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm            mResultText.announceForAccessibility(getResources().getString(R.string.desc_eq));
808ccc556621d3c0fe59c4878ce9c62322ff326041eHans Boehm            mResultText.announceForAccessibility(mResultText.getText());
809c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31Hans Boehm            setState(CalculatorState.ANIMATE);
81084614957604253d51296e06c97daced699a0a9deHans Boehm            final AnimatorSet animatorSet = new AnimatorSet();
81184614957604253d51296e06c97daced699a0a9deHans Boehm            animatorSet.playTogether(
8124459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    ObjectAnimator.ofPropertyValuesHolder(mResultText,
8134459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                            PropertyValuesHolder.ofFloat(View.SCALE_X, resultScale),
8144459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                            PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale),
8154459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)),
8164459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor),
8174459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y, formulaTranslationY));
8184459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            animatorSet.setDuration(getResources().getInteger(
8194459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen                    android.R.integer.config_longAnimTime));
82084614957604253d51296e06c97daced699a0a9deHans Boehm            animatorSet.addListener(new AnimatorListenerAdapter() {
82184614957604253d51296e06c97daced699a0a9deHans Boehm                @Override
82284614957604253d51296e06c97daced699a0a9deHans Boehm                public void onAnimationEnd(Animator animation) {
82384614957604253d51296e06c97daced699a0a9deHans Boehm                    setState(CalculatorState.RESULT);
82484614957604253d51296e06c97daced699a0a9deHans Boehm                    mCurrentAnimator = null;
82584614957604253d51296e06c97daced699a0a9deHans Boehm                }
82684614957604253d51296e06c97daced699a0a9deHans Boehm            });
82784614957604253d51296e06c97daced699a0a9deHans Boehm
82884614957604253d51296e06c97daced699a0a9deHans Boehm            mCurrentAnimator = animatorSet;
82984614957604253d51296e06c97daced699a0a9deHans Boehm            animatorSet.start();
83084614957604253d51296e06c97daced699a0a9deHans Boehm        } else /* No animation desired; get there fast, e.g. when restarting */ {
8314459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.setScaleX(resultScale);
8324459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.setScaleY(resultScale);
8334459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.setTranslationY(resultTranslationY);
8344459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen            mResultText.setTextColor(formulaTextColor);
83508e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm            mFormulaText.setTranslationY(formulaTranslationY);
8364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            setState(CalculatorState.RESULT);
83784614957604253d51296e06c97daced699a0a9deHans Boehm        }
83884614957604253d51296e06c97daced699a0a9deHans Boehm    }
8394b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
84084614957604253d51296e06c97daced699a0a9deHans Boehm    // Restore positions of the formula and result displays back to their original,
84184614957604253d51296e06c97daced699a0a9deHans Boehm    // pre-animation state.
84284614957604253d51296e06c97daced699a0a9deHans Boehm    private void restoreDisplayPositions() {
84384614957604253d51296e06c97daced699a0a9deHans Boehm        // Clear result.
8444459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setText("");
84584614957604253d51296e06c97daced699a0a9deHans Boehm        // Reset all of the values modified during the animation.
8464459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setScaleX(1.0f);
8474459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setScaleY(1.0f);
8484459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setTranslationX(0.0f);
8494459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        mResultText.setTranslationY(0.0f);
85008e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        mFormulaText.setTranslationY(0.0f);
8514b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
85208e8f322b0d93e06aaa2a15acc869dfd70791461Hans Boehm        mFormulaText.requestFocus();
8539192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm    }
8549192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm
8559192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm    @Override
8569192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm    public void onClick(AlertDialogFragment fragment, int which) {
8579192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm        if (which == DialogInterface.BUTTON_POSITIVE) {
8589192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm            // Timeout extension request.
8599192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm            mEvaluator.setLongTimeOut();
8609192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm        }
8619192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm    }
86284614957604253d51296e06c97daced699a0a9deHans Boehm
863d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    @Override
864d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    public boolean onCreateOptionsMenu(Menu menu) {
865d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        super.onCreateOptionsMenu(menu);
866d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen
867d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        getMenuInflater().inflate(R.menu.activity_calculator, menu);
868d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        return true;
869d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    }
870d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen
871d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    @Override
872d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    public boolean onPrepareOptionsMenu(Menu menu) {
873d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        super.onPrepareOptionsMenu(menu);
874d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen
875d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        // Show the leading option when displaying a result.
876d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT);
877d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen
878d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        // Show the fraction option when displaying a rational result.
879d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen        menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT
880d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen                && mEvaluator.getRational() != null);
881d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen
882d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen        return true;
88384614957604253d51296e06c97daced699a0a9deHans Boehm    }
8844b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
88584614957604253d51296e06c97daced699a0a9deHans Boehm    @Override
886d48b756434bda6a5f66740a8ea603aca1f536544Justin Klaassen    public boolean onOptionsItemSelected(MenuItem item) {
88784614957604253d51296e06c97daced699a0a9deHans Boehm        switch (item.getItemId()) {
888d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen            case R.id.menu_leading:
889d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen                displayFull();
89084614957604253d51296e06c97daced699a0a9deHans Boehm                return true;
8914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            case R.id.menu_fraction:
8924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                displayFraction();
8934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                return true;
894d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen            case R.id.menu_licenses:
895d36d63e0b261d896e5bcfdab09e3aecb7e4d086cJustin Klaassen                startActivity(new Intent(this, Licenses.class));
8964a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                return true;
89784614957604253d51296e06c97daced699a0a9deHans Boehm            default:
89884614957604253d51296e06c97daced699a0a9deHans Boehm                return super.onOptionsItemSelected(item);
89984614957604253d51296e06c97daced699a0a9deHans Boehm        }
90084614957604253d51296e06c97daced699a0a9deHans Boehm    }
90184614957604253d51296e06c97daced699a0a9deHans Boehm
9024a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void displayMessage(String s) {
9039192d5c751f2aaf1d18bb9b2f715905ffd9d5925Hans Boehm        AlertDialogFragment.showMessageDialog(this, s, null);
90484614957604253d51296e06c97daced699a0a9deHans Boehm    }
90584614957604253d51296e06c97daced699a0a9deHans Boehm
9064a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void displayFraction() {
9074a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        BoundedRational result = mEvaluator.getRational();
908013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm        displayMessage(KeyMaps.translateResult(result.toNiceString()));
9094a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
9104a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
9114a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    // Display full result to currently evaluated precision
9124a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    private void displayFull() {
9134a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        Resources res = getResources();
9144459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        String msg = mResultText.getFullText() + " ";
9154459516a2c116ddf80725d6a96a69186ccddc329Justin Klaassen        if (mResultText.fullTextIsExact()) {
9164a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            msg += res.getString(R.string.exact);
9174a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        } else {
9184a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            msg += res.getString(R.string.approximate);
9194a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
9204a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        displayMessage(msg);
9214a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    }
9224a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm
923017de9893efd33e179db10bc71189e150bc3486dHans Boehm    /**
924017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * Add input characters to the end of the expression.
925017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * Map them to the appropriate button pushes when possible.  Leftover characters
926017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * are added to mUnprocessedChars, which is presumed to immediately precede the newly
927017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * added characters.
928017de9893efd33e179db10bc71189e150bc3486dHans Boehm     * @param moreChars Characters to be added.
9290b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm     * @param explicit These characters were explicitly typed by the user, not pasted.
930017de9893efd33e179db10bc71189e150bc3486dHans Boehm     */
931017de9893efd33e179db10bc71189e150bc3486dHans Boehm    private void addChars(String moreChars, boolean explicit) {
9324a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        if (mUnprocessedChars != null) {
9334a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            moreChars = mUnprocessedChars + moreChars;
9344a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        }
9354a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        int current = 0;
9364a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        int len = moreChars.length();
9370b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm        boolean lastWasDigit = false;
938f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm        if (mCurrentState == CalculatorState.RESULT && len != 0) {
939f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm            // Clear display immediately for incomplete function name.
940f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm            switchToInput(KeyMaps.keyForChar(moreChars.charAt(current)));
941f6033a42aa90367012ecb11977fe86ccdac54f54Hans Boehm        }
9424a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        while (current < len) {
9434a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            char c = moreChars.charAt(current);
944013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm            int k = KeyMaps.keyForChar(c);
9450b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm            if (!explicit) {
9460b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                int expEnd;
9470b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                if (lastWasDigit && current !=
9480b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                        (expEnd = Evaluator.exponentEnd(moreChars, current))) {
9490b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    // Process scientific notation with 'E' when pasting, in spite of ambiguity
9500b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    // with base of natural log.
9510b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    // Otherwise the 10^x key is the user's friend.
9520b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    mEvaluator.addExponent(moreChars, current, expEnd);
9530b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    current = expEnd;
9540b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    lastWasDigit = false;
9550b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    continue;
9560b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                } else {
9570b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    boolean isDigit = KeyMaps.digVal(k) != KeyMaps.NOT_DIGIT;
9580b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    if (current == 0 && (isDigit || k == R.id.dec_point)
9590b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                            && mEvaluator.getExpr().hasTrailingConstant()) {
9600b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                        // Refuse to concatenate pasted content to trailing constant.
9610b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                        // This makes pasting of calculator results more consistent, whether or
9620b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                        // not the old calculator instance is still around.
9630b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                        addKeyToExpr(R.id.op_mul);
9640b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    }
9650b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                    lastWasDigit = (isDigit || lastWasDigit && k == R.id.dec_point);
9660b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm                }
9670b9806f624f25e7e0302da4cf55eda21f8c28163Hans Boehm            }
9684a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            if (k != View.NO_ID) {
9694a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                mCurrentButton = findViewById(k);
970017de9893efd33e179db10bc71189e150bc3486dHans Boehm                if (explicit) {
971017de9893efd33e179db10bc71189e150bc3486dHans Boehm                    addExplicitKeyToExpr(k);
972017de9893efd33e179db10bc71189e150bc3486dHans Boehm                } else {
973017de9893efd33e179db10bc71189e150bc3486dHans Boehm                    addKeyToExpr(k);
974017de9893efd33e179db10bc71189e150bc3486dHans Boehm                }
9754a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                if (Character.isSurrogate(c)) {
9764a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    current += 2;
9774a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                } else {
9784a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    ++current;
9794a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                }
9804a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                continue;
9814a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            }
982013969e98ce9e3eb4f87ec6159b06a74d07b2592Hans Boehm            int f = KeyMaps.funForString(moreChars, current);
9834a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            if (f != View.NO_ID) {
9844a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                mCurrentButton = findViewById(f);
985017de9893efd33e179db10bc71189e150bc3486dHans Boehm                if (explicit) {
986017de9893efd33e179db10bc71189e150bc3486dHans Boehm                    addExplicitKeyToExpr(f);
987017de9893efd33e179db10bc71189e150bc3486dHans Boehm                } else {
988017de9893efd33e179db10bc71189e150bc3486dHans Boehm                    addKeyToExpr(f);
989017de9893efd33e179db10bc71189e150bc3486dHans Boehm                }
9904a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                if (f == R.id.op_sqrt) {
9914a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    // Square root entered as function; don't lose the parenthesis.
9924a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                    addKeyToExpr(R.id.lparen);
9934a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                }
9944a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                current = moreChars.indexOf('(', current) + 1;
9954a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                continue;
9964b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen            }
9974a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            // There are characters left, but we can't convert them to button presses.
9984a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            mUnprocessedChars = moreChars.substring(current);
9994a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            redisplayAfterFormulaChange();
10004a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            return;
100184614957604253d51296e06c97daced699a0a9deHans Boehm        }
10024a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        mUnprocessedChars = null;
10034a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm        redisplayAfterFormulaChange();
100484614957604253d51296e06c97daced699a0a9deHans Boehm    }
10054b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen
10064a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm    @Override
1007fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen    public boolean onPaste(ClipData clip) {
1008fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0);
1009fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        if (item == null) {
1010fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen            // nothing to paste, bail early...
1011fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen            return false;
1012fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        }
1013fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen
1014fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        // Check if the item is a previously copied result, otherwise paste as raw text.
1015fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        final Uri uri = item.getUri();
1016fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        if (uri != null && mEvaluator.isLastSaved(uri)) {
10174a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            if (mCurrentState == CalculatorState.ERROR
1018fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen                    || mCurrentState == CalculatorState.RESULT) {
10194a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                setState(CalculatorState.INPUT);
10204a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm                mEvaluator.clear();
102184614957604253d51296e06c97daced699a0a9deHans Boehm            }
1022b13daf1050757fe3c69c2f0246de33e7e69b5fa9Hans Boehm            mEvaluator.appendSaved();
10234a6b7cb235c305761af5d7f40e74d4704e5058c8Hans Boehm            redisplayAfterFormulaChange();
1024fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        } else {
1025017de9893efd33e179db10bc71189e150bc3486dHans Boehm            addChars(item.coerceToText(this).toString(), false);
102684614957604253d51296e06c97daced699a0a9deHans Boehm        }
1027fc5ac82775fb126b8ec555cfd8af9f419cd707c6Justin Klaassen        return true;
10284b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen    }
10294b3af0578b1a44038856bc56244aea8aaeac22d1Justin Klaassen}
1030