blob: f4f3da75dea231710b60980d889964129022f6d9 [file] [log] [blame]
Justin Klaassen4b3af052014-05-27 17:53:10 -07001/*
Justin Klaassen12da1ad2016-04-04 14:20:37 -07002 * Copyright (C) 2016 The Android Open Source Project
Justin Klaassen4b3af052014-05-27 17:53:10 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Hans Boehm013969e2015-04-13 20:29:47 -070017// TODO: Copy & more general paste in formula? Note that this requires
18// great care: Currently the text version of a displayed formula
19// is not directly useful for re-evaluating the formula later, since
20// it contains ellipses representing subexpressions evaluated with
21// a different degree mode. Rather than supporting copy from the
22// formula window, we may eventually want to support generation of a
23// more useful text version in a separate window. It's not clear
24// this is worth the added (code and user) complexity.
Hans Boehm84614952014-11-25 18:46:17 -080025
Justin Klaassen4b3af052014-05-27 17:53:10 -070026package com.android.calculator2;
27
28import android.animation.Animator;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070029import android.animation.Animator.AnimatorListener;
Justin Klaassen4b3af052014-05-27 17:53:10 -070030import android.animation.AnimatorListenerAdapter;
31import android.animation.AnimatorSet;
Justin Klaassen4b3af052014-05-27 17:53:10 -070032import android.animation.ObjectAnimator;
Justin Klaassen44595162015-05-28 17:55:20 -070033import android.animation.PropertyValuesHolder;
Justin Klaassen9d33cdc2016-02-21 14:16:14 -080034import android.app.ActionBar;
Justin Klaassen4b3af052014-05-27 17:53:10 -070035import android.app.Activity;
Justin Klaassenfc5ac822015-06-18 13:15:17 -070036import android.content.ClipData;
Chenjie Yu3937b652016-06-01 23:14:26 -070037import android.content.Context;
Hans Boehm5e6a0ca2015-09-22 17:09:01 -070038import android.content.DialogInterface;
Justin Klaassend36d63e2015-05-05 12:59:36 -070039import android.content.Intent;
Hans Boehmbfe8c222015-04-02 16:26:07 -070040import android.content.res.Resources;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070041import android.graphics.Color;
Justin Klaassen8fff1442014-06-19 10:43:29 -070042import android.graphics.Rect;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070043import android.net.Uri;
Justin Klaassen4b3af052014-05-27 17:53:10 -070044import android.os.Bundle;
Justin Klaassenf79d6f62014-08-26 12:27:08 -070045import android.support.annotation.NonNull;
Chenjie Yu3937b652016-06-01 23:14:26 -070046import android.support.v4.content.ContextCompat;
Justin Klaassen3b4d13d2014-06-06 18:18:37 +010047import android.support.v4.view.ViewPager;
Annie Chine918fd22016-03-09 11:07:54 -080048import android.text.Editable;
Hans Boehm8a4f81c2015-07-09 10:41:25 -070049import android.text.SpannableStringBuilder;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070050import android.text.Spanned;
Annie Chinf360ef02016-03-10 13:45:39 -080051import android.text.TextUtils;
Annie Chine918fd22016-03-09 11:07:54 -080052import android.text.TextWatcher;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070053import android.text.style.ForegroundColorSpan;
Justin Klaassen44595162015-05-28 17:55:20 -070054import android.util.Property;
Annie Chine918fd22016-03-09 11:07:54 -080055import android.view.ActionMode;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070056import android.view.KeyCharacterMap;
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -070057import android.view.KeyEvent;
Hans Boehm84614952014-11-25 18:46:17 -080058import android.view.Menu;
59import android.view.MenuItem;
Justin Klaassen4b3af052014-05-27 17:53:10 -070060import android.view.View;
61import android.view.View.OnLongClickListener;
Justin Klaassen5f2a3342014-06-11 17:40:22 -070062import android.view.ViewAnimationUtils;
Justin Klaassen8fff1442014-06-19 10:43:29 -070063import android.view.ViewGroupOverlay;
Annie Chine918fd22016-03-09 11:07:54 -080064import android.view.ViewTreeObserver;
Justin Klaassen4b3af052014-05-27 17:53:10 -070065import android.view.animation.AccelerateDecelerateInterpolator;
Annie Chine918fd22016-03-09 11:07:54 -080066import android.widget.HorizontalScrollView;
Justin Klaassenfed941a2014-06-09 18:42:40 +010067import android.widget.TextView;
Justin Klaassend48b7562015-04-16 16:51:38 -070068import android.widget.Toolbar;
Justin Klaassenfed941a2014-06-09 18:42:40 +010069
Hans Boehm08e8f322015-04-21 13:18:38 -070070import com.android.calculator2.CalculatorText.OnTextSizeChangeListener;
Hans Boehm84614952014-11-25 18:46:17 -080071
72import java.io.ByteArrayInputStream;
Hans Boehm84614952014-11-25 18:46:17 -080073import java.io.ByteArrayOutputStream;
Hans Boehm84614952014-11-25 18:46:17 -080074import java.io.IOException;
Justin Klaassen721ec842015-05-28 14:30:08 -070075import java.io.ObjectInput;
76import java.io.ObjectInputStream;
77import java.io.ObjectOutput;
78import java.io.ObjectOutputStream;
Justin Klaassen4b3af052014-05-27 17:53:10 -070079
Justin Klaassen04f79c72014-06-27 17:25:35 -070080public class Calculator extends Activity
Hans Boehm5e6a0ca2015-09-22 17:09:01 -070081 implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener,
82 AlertDialogFragment.OnClickListener {
Justin Klaassen2be4fdb2014-08-06 19:54:09 -070083
84 /**
85 * Constant for an invalid resource id.
86 */
87 public static final int INVALID_RES_ID = -1;
Justin Klaassen4b3af052014-05-27 17:53:10 -070088
89 private enum CalculatorState {
Hans Boehm84614952014-11-25 18:46:17 -080090 INPUT, // Result and formula both visible, no evaluation requested,
91 // Though result may be visible on bottom line.
92 EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete.
Hans Boehmc1ea0912015-06-19 15:05:07 -070093 // Not used for instant result evaluation.
Hans Boehm84614952014-11-25 18:46:17 -080094 INIT, // Very temporary state used as alternative to EVALUATE
95 // during reinitialization. Do not animate on completion.
96 ANIMATE, // Result computed, animation to enlarge result window in progress.
97 RESULT, // Result displayed, formula invisible.
98 // If we are in RESULT state, the formula was evaluated without
99 // error to initial precision.
100 ERROR // Error displayed: Formula visible, result shows error message.
101 // Display similar to INPUT state.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700102 }
Hans Boehm84614952014-11-25 18:46:17 -0800103 // Normal transition sequence is
104 // INPUT -> EVALUATE -> ANIMATE -> RESULT (or ERROR) -> INPUT
105 // A RESULT -> ERROR transition is possible in rare corner cases, in which
106 // a higher precision evaluation exposes an error. This is possible, since we
107 // initially evaluate assuming we were given a well-defined problem. If we
108 // were actually asked to compute sqrt(<extremely tiny negative number>) we produce 0
109 // unless we are asked for enough precision that we can distinguish the argument from zero.
110 // TODO: Consider further heuristics to reduce the chance of observing this?
111 // It already seems to be observable only in contrived cases.
112 // ANIMATE, ERROR, and RESULT are translated to an INIT state if the application
113 // is restarted in that state. This leads us to recompute and redisplay the result
114 // ASAP.
115 // TODO: Possibly save a bit more information, e.g. its initial display string
116 // or most significant digit position, to speed up restart.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700117
Justin Klaassen44595162015-05-28 17:55:20 -0700118 private final Property<TextView, Integer> TEXT_COLOR =
119 new Property<TextView, Integer>(Integer.class, "textColor") {
120 @Override
121 public Integer get(TextView textView) {
122 return textView.getCurrentTextColor();
123 }
124
125 @Override
126 public void set(TextView textView, Integer textColor) {
127 textView.setTextColor(textColor);
128 }
129 };
130
Justin Klaassen3e223ea2016-02-05 14:18:06 -0800131 private static final String NAME = "Calculator";
Hans Boehm84614952014-11-25 18:46:17 -0800132 private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
Hans Boehm760a9dc2015-04-20 10:27:12 -0700133 private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
Justin Klaassen3e223ea2016-02-05 14:18:06 -0800134 /**
135 * Associated value is a byte array holding the evaluator state.
136 */
Hans Boehm84614952014-11-25 18:46:17 -0800137 private static final String KEY_EVAL_STATE = NAME + "_eval_state";
Justin Klaassen3e223ea2016-02-05 14:18:06 -0800138 private static final String KEY_INVERSE_MODE = NAME + "_inverse_mode";
Justin Klaassen741471e2014-06-11 09:43:44 -0700139
Annie Chine918fd22016-03-09 11:07:54 -0800140 private final ViewTreeObserver.OnPreDrawListener mPreDrawListener =
141 new ViewTreeObserver.OnPreDrawListener() {
142 @Override
143 public boolean onPreDraw() {
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700144 mFormulaContainer.scrollTo(mFormulaText.getRight(), 0);
Annie Chine918fd22016-03-09 11:07:54 -0800145 final ViewTreeObserver observer = mFormulaContainer.getViewTreeObserver();
146 if (observer.isAlive()) {
147 observer.removeOnPreDrawListener(this);
148 }
149 return false;
150 }
151 };
152
153 private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
154 @Override
155 public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
156 }
157
158 @Override
159 public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
160 }
161
162 @Override
163 public void afterTextChanged(Editable editable) {
164 final ViewTreeObserver observer = mFormulaContainer.getViewTreeObserver();
165 if (observer.isAlive()) {
166 observer.removeOnPreDrawListener(mPreDrawListener);
167 observer.addOnPreDrawListener(mPreDrawListener);
168 }
169 }
170 };
171
Justin Klaassen4b3af052014-05-27 17:53:10 -0700172 private CalculatorState mCurrentState;
Hans Boehm84614952014-11-25 18:46:17 -0800173 private Evaluator mEvaluator;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700174
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800175 private CalculatorDisplay mDisplayView;
Justin Klaassend48b7562015-04-16 16:51:38 -0700176 private TextView mModeView;
Hans Boehm08e8f322015-04-21 13:18:38 -0700177 private CalculatorText mFormulaText;
Justin Klaassen44595162015-05-28 17:55:20 -0700178 private CalculatorResult mResultText;
Annie Chine918fd22016-03-09 11:07:54 -0800179 private HorizontalScrollView mFormulaContainer;
Justin Klaassend48b7562015-04-16 16:51:38 -0700180
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100181 private ViewPager mPadViewPager;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700182 private View mDeleteButton;
183 private View mClearButton;
Justin Klaassend48b7562015-04-16 16:51:38 -0700184 private View mEqualButton;
Justin Klaassene2711cb2015-05-28 11:13:17 -0700185
186 private TextView mInverseToggle;
187 private TextView mModeToggle;
188
Justin Klaassen721ec842015-05-28 14:30:08 -0700189 private View[] mInvertibleButtons;
Justin Klaassene2711cb2015-05-28 11:13:17 -0700190 private View[] mInverseButtons;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700191
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700192 private View mCurrentButton;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700193 private Animator mCurrentAnimator;
194
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700195 // Characters that were recently entered at the end of the display that have not yet
196 // been added to the underlying expression.
197 private String mUnprocessedChars = null;
198
199 // Color to highlight unprocessed characters from physical keyboard.
200 // TODO: should probably match this to the error color?
201 private ForegroundColorSpan mUnprocessedColorSpan = new ForegroundColorSpan(Color.RED);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700202
Annie Chin26e159e2016-05-18 15:17:14 -0700203 // Whether the display is one line.
204 private boolean mOneLine;
205
Justin Klaassen4b3af052014-05-27 17:53:10 -0700206 @Override
207 protected void onCreate(Bundle savedInstanceState) {
208 super.onCreate(savedInstanceState);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700209 setContentView(R.layout.activity_calculator);
Justin Klaassend48b7562015-04-16 16:51:38 -0700210 setActionBar((Toolbar) findViewById(R.id.toolbar));
211
212 // Hide all default options in the ActionBar.
213 getActionBar().setDisplayOptions(0);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700214
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800215 // Ensure the toolbar stays visible while the options menu is displayed.
216 getActionBar().addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() {
217 @Override
218 public void onMenuVisibilityChanged(boolean isVisible) {
219 mDisplayView.setForceToolbarVisible(isVisible);
220 }
221 });
222
223 mDisplayView = (CalculatorDisplay) findViewById(R.id.display);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700224 mModeView = (TextView) findViewById(R.id.mode);
Hans Boehm08e8f322015-04-21 13:18:38 -0700225 mFormulaText = (CalculatorText) findViewById(R.id.formula);
Justin Klaassen44595162015-05-28 17:55:20 -0700226 mResultText = (CalculatorResult) findViewById(R.id.result);
Annie Chine918fd22016-03-09 11:07:54 -0800227 mFormulaContainer = (HorizontalScrollView) findViewById(R.id.formula_container);
Justin Klaassend48b7562015-04-16 16:51:38 -0700228
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100229 mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700230 mDeleteButton = findViewById(R.id.del);
231 mClearButton = findViewById(R.id.clr);
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700232 mEqualButton = findViewById(R.id.pad_numeric).findViewById(R.id.eq);
233 if (mEqualButton == null || mEqualButton.getVisibility() != View.VISIBLE) {
234 mEqualButton = findViewById(R.id.pad_operator).findViewById(R.id.eq);
235 }
Justin Klaassene2711cb2015-05-28 11:13:17 -0700236
237 mInverseToggle = (TextView) findViewById(R.id.toggle_inv);
238 mModeToggle = (TextView) findViewById(R.id.toggle_mode);
239
Annie Chin26e159e2016-05-18 15:17:14 -0700240 mOneLine = mResultText.getVisibility() == View.INVISIBLE;
241
Justin Klaassen721ec842015-05-28 14:30:08 -0700242 mInvertibleButtons = new View[] {
243 findViewById(R.id.fun_sin),
244 findViewById(R.id.fun_cos),
Hans Boehm4db31b42015-05-31 12:19:05 -0700245 findViewById(R.id.fun_tan),
246 findViewById(R.id.fun_ln),
247 findViewById(R.id.fun_log),
248 findViewById(R.id.op_sqrt)
Justin Klaassene2711cb2015-05-28 11:13:17 -0700249 };
250 mInverseButtons = new View[] {
251 findViewById(R.id.fun_arcsin),
252 findViewById(R.id.fun_arccos),
Hans Boehm4db31b42015-05-31 12:19:05 -0700253 findViewById(R.id.fun_arctan),
254 findViewById(R.id.fun_exp),
255 findViewById(R.id.fun_10pow),
256 findViewById(R.id.op_sqr)
Justin Klaassene2711cb2015-05-28 11:13:17 -0700257 };
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700258
Justin Klaassen44595162015-05-28 17:55:20 -0700259 mEvaluator = new Evaluator(this, mResultText);
260 mResultText.setEvaluator(mEvaluator);
Hans Boehm013969e2015-04-13 20:29:47 -0700261 KeyMaps.setActivity(this);
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700262
Hans Boehm84614952014-11-25 18:46:17 -0800263 if (savedInstanceState != null) {
264 setState(CalculatorState.values()[
265 savedInstanceState.getInt(KEY_DISPLAY_STATE,
266 CalculatorState.INPUT.ordinal())]);
Hans Boehm760a9dc2015-04-20 10:27:12 -0700267 CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
268 if (unprocessed != null) {
269 mUnprocessedChars = unprocessed.toString();
270 }
271 byte[] state = savedInstanceState.getByteArray(KEY_EVAL_STATE);
Hans Boehm84614952014-11-25 18:46:17 -0800272 if (state != null) {
273 try (ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(state))) {
274 mEvaluator.restoreInstanceState(in);
275 } catch (Throwable ignored) {
276 // When in doubt, revert to clean state
277 mCurrentState = CalculatorState.INPUT;
278 mEvaluator.clear();
279 }
280 }
Hans Boehmfbcef702015-04-27 18:07:47 -0700281 } else {
282 mCurrentState = CalculatorState.INPUT;
283 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800284 }
Justin Klaassene2711cb2015-05-28 11:13:17 -0700285
Hans Boehm08e8f322015-04-21 13:18:38 -0700286 mFormulaText.setOnTextSizeChangeListener(this);
Justin Klaassenfc5ac822015-06-18 13:15:17 -0700287 mFormulaText.setOnPasteListener(this);
Annie Chine918fd22016-03-09 11:07:54 -0800288 mFormulaText.addTextChangedListener(mFormulaTextWatcher);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700289 mDeleteButton.setOnLongClickListener(this);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700290
Justin Klaassen3e223ea2016-02-05 14:18:06 -0800291 onInverseToggled(savedInstanceState != null
292 && savedInstanceState.getBoolean(KEY_INVERSE_MODE));
Justin Klaassene2711cb2015-05-28 11:13:17 -0700293 onModeChanged(mEvaluator.getDegreeMode());
294
Hans Boehm84614952014-11-25 18:46:17 -0800295 if (mCurrentState != CalculatorState.INPUT) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700296 // Just reevaluate.
297 redisplayFormula();
Hans Boehm84614952014-11-25 18:46:17 -0800298 setState(CalculatorState.INIT);
Hans Boehm84614952014-11-25 18:46:17 -0800299 mEvaluator.requireResult();
300 } else {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700301 redisplayAfterFormulaChange();
Hans Boehm84614952014-11-25 18:46:17 -0800302 }
303 // TODO: We're currently not saving and restoring scroll position.
304 // We probably should. Details may require care to deal with:
305 // - new display size
306 // - slow recomputation if we've scrolled far.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700307 }
308
309 @Override
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800310 protected void onResume() {
311 super.onResume();
312
Hans Boehm52d477a2016-04-01 17:42:50 -0700313 // Always temporarily show the toolbar initially on launch.
314 showAndMaybeHideToolbar();
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800315 }
316
317 @Override
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700318 protected void onSaveInstanceState(@NonNull Bundle outState) {
Hans Boehm40125442016-01-22 10:35:35 -0800319 mEvaluator.cancelAll(true);
Justin Klaassenf79d6f62014-08-26 12:27:08 -0700320 // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
321 if (mCurrentAnimator != null) {
322 mCurrentAnimator.cancel();
323 }
324
Justin Klaassen4b3af052014-05-27 17:53:10 -0700325 super.onSaveInstanceState(outState);
Hans Boehm84614952014-11-25 18:46:17 -0800326 outState.putInt(KEY_DISPLAY_STATE, mCurrentState.ordinal());
Hans Boehm760a9dc2015-04-20 10:27:12 -0700327 outState.putCharSequence(KEY_UNPROCESSED_CHARS, mUnprocessedChars);
Hans Boehm84614952014-11-25 18:46:17 -0800328 ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
329 try (ObjectOutput out = new ObjectOutputStream(byteArrayStream)) {
330 mEvaluator.saveInstanceState(out);
331 } catch (IOException e) {
332 // Impossible; No IO involved.
333 throw new AssertionError("Impossible IO exception", e);
334 }
335 outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
Justin Klaassen3e223ea2016-02-05 14:18:06 -0800336 outState.putBoolean(KEY_INVERSE_MODE, mInverseToggle.isSelected());
Justin Klaassen4b3af052014-05-27 17:53:10 -0700337 }
338
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700339 // Set the state, updating delete label and display colors.
340 // This restores display positions on moving to INPUT.
Justin Klaassend48b7562015-04-16 16:51:38 -0700341 // But movement/animation for moving to RESULT has already been done.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700342 private void setState(CalculatorState state) {
343 if (mCurrentState != state) {
Hans Boehm84614952014-11-25 18:46:17 -0800344 if (state == CalculatorState.INPUT) {
345 restoreDisplayPositions();
346 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700347 mCurrentState = state;
348
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700349 if (mCurrentState == CalculatorState.RESULT) {
350 // No longer do this for ERROR; allow mistakes to be corrected.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700351 mDeleteButton.setVisibility(View.GONE);
352 mClearButton.setVisibility(View.VISIBLE);
353 } else {
354 mDeleteButton.setVisibility(View.VISIBLE);
355 mClearButton.setVisibility(View.GONE);
356 }
357
Annie Chin26e159e2016-05-18 15:17:14 -0700358 if (mOneLine) {
359 if (mCurrentState == CalculatorState.RESULT
360 || mCurrentState == CalculatorState.EVALUATE
361 || mCurrentState == CalculatorState.ANIMATE) {
362 mFormulaText.setVisibility(View.VISIBLE);
363 mResultText.setVisibility(View.VISIBLE);
Annie Chin947d93b2016-06-14 10:18:54 -0700364 } else if (mCurrentState == CalculatorState.ERROR) {
365 mFormulaText.setVisibility(View.INVISIBLE);
366 mResultText.setVisibility(View.VISIBLE);
Annie Chin26e159e2016-05-18 15:17:14 -0700367 } else {
368 mFormulaText.setVisibility(View.VISIBLE);
369 mResultText.setVisibility(View.INVISIBLE);
370 }
371 }
372
Hans Boehm84614952014-11-25 18:46:17 -0800373 if (mCurrentState == CalculatorState.ERROR) {
Chenjie Yu3937b652016-06-01 23:14:26 -0700374 final int errorColor =
375 ContextCompat.getColor(this, R.color.calculator_error_color);
Hans Boehm08e8f322015-04-21 13:18:38 -0700376 mFormulaText.setTextColor(errorColor);
Justin Klaassen44595162015-05-28 17:55:20 -0700377 mResultText.setTextColor(errorColor);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700378 getWindow().setStatusBarColor(errorColor);
Justin Klaassen44595162015-05-28 17:55:20 -0700379 } else if (mCurrentState != CalculatorState.RESULT) {
Chenjie Yu3937b652016-06-01 23:14:26 -0700380 mFormulaText.setTextColor(
381 ContextCompat.getColor(this, R.color.display_formula_text_color));
382 mResultText.setTextColor(
383 ContextCompat.getColor(this, R.color.display_result_text_color));
384 getWindow().setStatusBarColor(
385 ContextCompat.getColor(this, R.color.calculator_accent_color));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700386 }
Justin Klaassend48b7562015-04-16 16:51:38 -0700387
388 invalidateOptionsMenu();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700389 }
390 }
391
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700392 @Override
393 public void onActionModeStarted(ActionMode mode) {
394 super.onActionModeStarted(mode);
395 if (mode.getTag() == CalculatorText.TAG_ACTION_MODE) {
396 mFormulaContainer.scrollTo(mFormulaText.getRight(), 0);
397 }
398 }
399
Chenjie Yu3937b652016-06-01 23:14:26 -0700400 /**
401 * Stop any active ActionMode or ContextMenu for copy/paste actions.
402 * Return true if there was one.
403 */
404 private boolean stopActionModeOrContextMenu() {
405 if (mResultText.stopActionModeOrContextMenu()) {
Hans Boehm1176f232015-05-11 16:26:03 -0700406 return true;
407 }
Chenjie Yu3937b652016-06-01 23:14:26 -0700408 if (mFormulaText.stopActionModeOrContextMenu()) {
Hans Boehm1176f232015-05-11 16:26:03 -0700409 return true;
410 }
411 return false;
412 }
413
Justin Klaassen4b3af052014-05-27 17:53:10 -0700414 @Override
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700415 public void onUserInteraction() {
416 super.onUserInteraction();
417
418 // If there's an animation in progress, end it immediately, so the user interaction can
419 // be handled.
420 if (mCurrentAnimator != null) {
421 mCurrentAnimator.end();
422 }
423 }
424
425 @Override
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100426 public void onBackPressed() {
Chenjie Yu3937b652016-06-01 23:14:26 -0700427 if (!stopActionModeOrContextMenu()) {
Hans Boehm1176f232015-05-11 16:26:03 -0700428 if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
429 // Select the previous pad.
430 mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
431 } else {
432 // If the user is currently looking at the first pad (or the pad is not paged),
433 // allow the system to handle the Back button.
434 super.onBackPressed();
435 }
Justin Klaassen3b4d13d2014-06-06 18:18:37 +0100436 }
437 }
438
439 @Override
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700440 public boolean onKeyUp(int keyCode, KeyEvent event) {
Justin Klaassen83959da2016-04-06 11:55:24 -0700441 // Allow the system to handle special key codes (e.g. "BACK" or "DPAD").
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700442 switch (keyCode) {
Justin Klaassen83959da2016-04-06 11:55:24 -0700443 case KeyEvent.KEYCODE_BACK:
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700444 case KeyEvent.KEYCODE_DPAD_UP:
445 case KeyEvent.KEYCODE_DPAD_DOWN:
446 case KeyEvent.KEYCODE_DPAD_LEFT:
447 case KeyEvent.KEYCODE_DPAD_RIGHT:
448 return super.onKeyUp(keyCode, event);
449 }
450
Chenjie Yu3937b652016-06-01 23:14:26 -0700451 // Stop the action mode or context menu if it's showing.
452 stopActionModeOrContextMenu();
Justin Klaassend12e0622016-04-27 16:26:47 -0700453
Justin Klaassen12da1ad2016-04-04 14:20:37 -0700454 // Always cancel unrequested in-progress evaluation, so that we don't have to worry about
455 // subsequent asynchronous completion.
456 // Requested in-progress evaluations are handled below.
457 if (mCurrentState != CalculatorState.EVALUATE) {
458 mEvaluator.cancelAll(true);
459 }
460
461 switch (keyCode) {
462 case KeyEvent.KEYCODE_NUMPAD_ENTER:
463 case KeyEvent.KEYCODE_ENTER:
464 case KeyEvent.KEYCODE_DPAD_CENTER:
465 mCurrentButton = mEqualButton;
466 onEquals();
467 return true;
468 case KeyEvent.KEYCODE_DEL:
469 mCurrentButton = mDeleteButton;
470 onDelete();
471 return true;
472 default:
473 cancelIfEvaluating(false);
474 final int raw = event.getKeyCharacterMap().get(keyCode, event.getMetaState());
475 if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
476 return true; // discard
477 }
478 // Try to discard non-printing characters and the like.
479 // The user will have to explicitly delete other junk that gets past us.
480 if (Character.isIdentifierIgnorable(raw) || Character.isWhitespace(raw)) {
481 return true;
482 }
483 char c = (char) raw;
484 if (c == '=') {
485 mCurrentButton = mEqualButton;
486 onEquals();
487 } else {
488 addChars(String.valueOf(c), true);
489 redisplayAfterFormulaChange();
490 }
491 return true;
Justin Klaassen4b3af052014-05-27 17:53:10 -0700492 }
493 }
494
Justin Klaassene2711cb2015-05-28 11:13:17 -0700495 /**
496 * Invoked whenever the inverse button is toggled to update the UI.
497 *
498 * @param showInverse {@code true} if inverse functions should be shown
499 */
500 private void onInverseToggled(boolean showInverse) {
Justin Klaassen3e223ea2016-02-05 14:18:06 -0800501 mInverseToggle.setSelected(showInverse);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700502 if (showInverse) {
503 mInverseToggle.setContentDescription(getString(R.string.desc_inv_on));
Justin Klaassen721ec842015-05-28 14:30:08 -0700504 for (View invertibleButton : mInvertibleButtons) {
505 invertibleButton.setVisibility(View.GONE);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700506 }
507 for (View inverseButton : mInverseButtons) {
508 inverseButton.setVisibility(View.VISIBLE);
509 }
510 } else {
511 mInverseToggle.setContentDescription(getString(R.string.desc_inv_off));
Justin Klaassen721ec842015-05-28 14:30:08 -0700512 for (View invertibleButton : mInvertibleButtons) {
513 invertibleButton.setVisibility(View.VISIBLE);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700514 }
515 for (View inverseButton : mInverseButtons) {
516 inverseButton.setVisibility(View.GONE);
517 }
518 }
519 }
520
521 /**
522 * Invoked whenever the deg/rad mode may have changed to update the UI.
523 *
524 * @param degreeMode {@code true} if in degree mode
525 */
526 private void onModeChanged(boolean degreeMode) {
527 if (degreeMode) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700528 mModeView.setText(R.string.mode_deg);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700529 mModeView.setContentDescription(getString(R.string.desc_mode_deg));
530
531 mModeToggle.setText(R.string.mode_rad);
532 mModeToggle.setContentDescription(getString(R.string.desc_switch_rad));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700533 } else {
Justin Klaassend48b7562015-04-16 16:51:38 -0700534 mModeView.setText(R.string.mode_rad);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700535 mModeView.setContentDescription(getString(R.string.desc_mode_rad));
536
537 mModeToggle.setText(R.string.mode_deg);
538 mModeToggle.setContentDescription(getString(R.string.desc_switch_deg));
Hans Boehmbfe8c222015-04-02 16:26:07 -0700539 }
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800540
541 // Show the toolbar to highlight the mode change.
Hans Boehm52d477a2016-04-01 17:42:50 -0700542 showAndMaybeHideToolbar();
Hans Boehmbfe8c222015-04-02 16:26:07 -0700543 }
Hans Boehm84614952014-11-25 18:46:17 -0800544
Hans Boehm5d79d102015-09-16 16:33:47 -0700545 /**
546 * Switch to INPUT from RESULT state in response to input of the specified button_id.
547 * View.NO_ID is treated as an incomplete function id.
548 */
549 private void switchToInput(int button_id) {
550 if (KeyMaps.isBinary(button_id) || KeyMaps.isSuffix(button_id)) {
551 mEvaluator.collapse();
552 } else {
553 announceClearedForAccessibility();
554 mEvaluator.clear();
555 }
556 setState(CalculatorState.INPUT);
557 }
558
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700559 // Add the given button id to input expression.
560 // If appropriate, clear the expression before doing so.
561 private void addKeyToExpr(int id) {
562 if (mCurrentState == CalculatorState.ERROR) {
563 setState(CalculatorState.INPUT);
564 } else if (mCurrentState == CalculatorState.RESULT) {
Hans Boehm5d79d102015-09-16 16:33:47 -0700565 switchToInput(id);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700566 }
567 if (!mEvaluator.append(id)) {
568 // TODO: Some user visible feedback?
569 }
570 }
571
Hans Boehm017de982015-06-10 17:46:03 -0700572 /**
573 * Add the given button id to input expression, assuming it was explicitly
574 * typed/touched.
575 * We perform slightly more aggressive correction than in pasted expressions.
576 */
577 private void addExplicitKeyToExpr(int id) {
578 if (mCurrentState == CalculatorState.INPUT && id == R.id.op_sub) {
579 mEvaluator.getExpr().removeTrailingAdditiveOperators();
580 }
581 addKeyToExpr(id);
582 }
583
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700584 private void redisplayAfterFormulaChange() {
585 // TODO: Could do this more incrementally.
586 redisplayFormula();
587 setState(CalculatorState.INPUT);
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800588 if (haveUnprocessed()) {
Justin Klaassen44595162015-05-28 17:55:20 -0700589 mResultText.clear();
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800590 // Force reevaluation when text is deleted, even if expression is unchanged.
591 mEvaluator.touch();
592 } else {
593 if (mEvaluator.getExpr().hasInterestingOps()) {
594 mEvaluator.evaluateAndShowResult();
595 } else {
596 mResultText.clear();
597 }
Hans Boehmc023b732015-04-29 11:30:47 -0700598 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700599 }
600
Hans Boehm52d477a2016-04-01 17:42:50 -0700601 /**
602 * Show the toolbar.
603 * Automatically hide it again if it's not relevant to current formula.
604 */
605 private void showAndMaybeHideToolbar() {
606 final boolean shouldBeVisible =
607 mCurrentState == CalculatorState.INPUT && mEvaluator.hasTrigFuncs();
608 mDisplayView.showToolbar(!shouldBeVisible);
609 }
610
611 /**
612 * Display or hide the toolbar depending on calculator state.
613 */
614 private void showOrHideToolbar() {
615 final boolean shouldBeVisible =
616 mCurrentState == CalculatorState.INPUT && mEvaluator.hasTrigFuncs();
617 if (shouldBeVisible) {
618 mDisplayView.showToolbar(false);
619 } else {
620 mDisplayView.hideToolbar();
621 }
622 }
623
Justin Klaassen4b3af052014-05-27 17:53:10 -0700624 public void onButtonClick(View view) {
Hans Boehmc1ea0912015-06-19 15:05:07 -0700625 // Any animation is ended before we get here.
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700626 mCurrentButton = view;
Chenjie Yu3937b652016-06-01 23:14:26 -0700627 stopActionModeOrContextMenu();
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800628
Hans Boehmc1ea0912015-06-19 15:05:07 -0700629 // See onKey above for the rationale behind some of the behavior below:
630 if (mCurrentState != CalculatorState.EVALUATE) {
631 // Cancel evaluations that were not specifically requested.
632 mEvaluator.cancelAll(true);
Hans Boehm84614952014-11-25 18:46:17 -0800633 }
Justin Klaassen9d33cdc2016-02-21 14:16:14 -0800634
Justin Klaassend48b7562015-04-16 16:51:38 -0700635 final int id = view.getId();
Hans Boehm84614952014-11-25 18:46:17 -0800636 switch (id) {
Justin Klaassen4b3af052014-05-27 17:53:10 -0700637 case R.id.eq:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700638 onEquals();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700639 break;
640 case R.id.del:
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700641 onDelete();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700642 break;
643 case R.id.clr:
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700644 onClear();
Hans Boehm52d477a2016-04-01 17:42:50 -0700645 return; // Toolbar visibility adjusted at end of animation.
Justin Klaassene2711cb2015-05-28 11:13:17 -0700646 case R.id.toggle_inv:
647 final boolean selected = !mInverseToggle.isSelected();
648 mInverseToggle.setSelected(selected);
649 onInverseToggled(selected);
Hans Boehmc1ea0912015-06-19 15:05:07 -0700650 if (mCurrentState == CalculatorState.RESULT) {
651 mResultText.redisplay(); // In case we cancelled reevaluation.
652 }
Justin Klaassene2711cb2015-05-28 11:13:17 -0700653 break;
654 case R.id.toggle_mode:
Hans Boehmc1ea0912015-06-19 15:05:07 -0700655 cancelIfEvaluating(false);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700656 final boolean mode = !mEvaluator.getDegreeMode();
Hans Boehmbfe8c222015-04-02 16:26:07 -0700657 if (mCurrentState == CalculatorState.RESULT) {
658 mEvaluator.collapse(); // Capture result evaluated in old mode
659 redisplayFormula();
660 }
661 // In input mode, we reinterpret already entered trig functions.
662 mEvaluator.setDegreeMode(mode);
Justin Klaassene2711cb2015-05-28 11:13:17 -0700663 onModeChanged(mode);
Hans Boehmbfe8c222015-04-02 16:26:07 -0700664 setState(CalculatorState.INPUT);
Justin Klaassen44595162015-05-28 17:55:20 -0700665 mResultText.clear();
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800666 if (!haveUnprocessed() && mEvaluator.getExpr().hasInterestingOps()) {
Hans Boehmc023b732015-04-29 11:30:47 -0700667 mEvaluator.evaluateAndShowResult();
668 }
Hans Boehm52d477a2016-04-01 17:42:50 -0700669 return; // onModeChanged adjusted toolbar visibility.
Justin Klaassen4b3af052014-05-27 17:53:10 -0700670 default:
Hans Boehmc1ea0912015-06-19 15:05:07 -0700671 cancelIfEvaluating(false);
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800672 if (haveUnprocessed()) {
673 // For consistency, append as uninterpreted characters.
674 // This may actually be useful for a left parenthesis.
675 addChars(KeyMaps.toString(this, id), true);
676 } else {
677 addExplicitKeyToExpr(id);
678 redisplayAfterFormulaChange();
679 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700680 break;
681 }
Hans Boehm52d477a2016-04-01 17:42:50 -0700682 showOrHideToolbar();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700683 }
684
Hans Boehm84614952014-11-25 18:46:17 -0800685 void redisplayFormula() {
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700686 SpannableStringBuilder formula = mEvaluator.getExpr().toSpannableStringBuilder(this);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700687 if (mUnprocessedChars != null) {
688 // Add and highlight characters we couldn't process.
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700689 formula.append(mUnprocessedChars, mUnprocessedColorSpan,
690 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700691 }
Hans Boehm8a4f81c2015-07-09 10:41:25 -0700692 mFormulaText.changeTextTo(formula);
Annie Chinf360ef02016-03-10 13:45:39 -0800693 mFormulaText.setContentDescription(TextUtils.isEmpty(formula)
694 ? getString(R.string.desc_formula) : formula.toString());
Hans Boehm84614952014-11-25 18:46:17 -0800695 }
696
Justin Klaassen4b3af052014-05-27 17:53:10 -0700697 @Override
698 public boolean onLongClick(View view) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700699 mCurrentButton = view;
700
Justin Klaassen4b3af052014-05-27 17:53:10 -0700701 if (view.getId() == R.id.del) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700702 onClear();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700703 return true;
704 }
705 return false;
706 }
707
Hans Boehm84614952014-11-25 18:46:17 -0800708 // Initial evaluation completed successfully. Initiate display.
Hans Boehma0e45f32015-05-30 13:20:35 -0700709 public void onEvaluate(int initDisplayPrec, int msd, int leastDigPos,
710 String truncatedWholeNumber) {
Justin Klaassend48b7562015-04-16 16:51:38 -0700711 // Invalidate any options that may depend on the current result.
712 invalidateOptionsMenu();
713
Hans Boehma0e45f32015-05-30 13:20:35 -0700714 mResultText.displayResult(initDisplayPrec, msd, leastDigPos, truncatedWholeNumber);
Hans Boehm61568a12015-05-18 18:25:41 -0700715 if (mCurrentState != CalculatorState.INPUT) { // in EVALUATE or INIT state
Hans Boehm84614952014-11-25 18:46:17 -0800716 onResult(mCurrentState != CalculatorState.INIT);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700717 }
Hans Boehm84614952014-11-25 18:46:17 -0800718 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700719
Hans Boehmc1ea0912015-06-19 15:05:07 -0700720 // Reset state to reflect evaluator cancellation. Invoked by evaluator.
Hans Boehm84614952014-11-25 18:46:17 -0800721 public void onCancelled() {
722 // We should be in EVALUATE state.
Hans Boehm84614952014-11-25 18:46:17 -0800723 setState(CalculatorState.INPUT);
Justin Klaassen44595162015-05-28 17:55:20 -0700724 mResultText.clear();
Hans Boehm84614952014-11-25 18:46:17 -0800725 }
726
727 // Reevaluation completed; ask result to redisplay current value.
728 public void onReevaluate()
729 {
Justin Klaassen44595162015-05-28 17:55:20 -0700730 mResultText.redisplay();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700731 }
732
Justin Klaassenfed941a2014-06-09 18:42:40 +0100733 @Override
734 public void onTextSizeChanged(final TextView textView, float oldSize) {
735 if (mCurrentState != CalculatorState.INPUT) {
736 // Only animate text changes that occur from user input.
737 return;
738 }
739
740 // Calculate the values needed to perform the scale and translation animations,
741 // maintaining the same apparent baseline for the displayed text.
742 final float textScale = oldSize / textView.getTextSize();
743 final float translationX = (1.0f - textScale) *
744 (textView.getWidth() / 2.0f - textView.getPaddingEnd());
745 final float translationY = (1.0f - textScale) *
746 (textView.getHeight() / 2.0f - textView.getPaddingBottom());
747
748 final AnimatorSet animatorSet = new AnimatorSet();
749 animatorSet.playTogether(
750 ObjectAnimator.ofFloat(textView, View.SCALE_X, textScale, 1.0f),
751 ObjectAnimator.ofFloat(textView, View.SCALE_Y, textScale, 1.0f),
752 ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, translationX, 0.0f),
753 ObjectAnimator.ofFloat(textView, View.TRANSLATION_Y, translationY, 0.0f));
Justin Klaassen94db7202014-06-11 11:22:31 -0700754 animatorSet.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassenfed941a2014-06-09 18:42:40 +0100755 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
756 animatorSet.start();
757 }
758
Hans Boehmc1ea0912015-06-19 15:05:07 -0700759 /**
760 * Cancel any in-progress explicitly requested evaluations.
761 * @param quiet suppress pop-up message. Explicit evaluation can change the expression
762 value, and certainly changes the display, so it seems reasonable to warn.
763 * @return true if there was such an evaluation
764 */
765 private boolean cancelIfEvaluating(boolean quiet) {
766 if (mCurrentState == CalculatorState.EVALUATE) {
767 mEvaluator.cancelAll(quiet);
768 return true;
769 } else {
770 return false;
771 }
772 }
773
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800774 private boolean haveUnprocessed() {
775 return mUnprocessedChars != null && !mUnprocessedChars.isEmpty();
776 }
777
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700778 private void onEquals() {
Hans Boehm56d6e762016-06-06 11:46:29 -0700779 // Ignore if in non-INPUT state, or if there are no operators.
780 if (mCurrentState == CalculatorState.INPUT && mEvaluator.getExpr().hasInterestingOps()) {
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700781 setState(CalculatorState.EVALUATE);
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800782 if (haveUnprocessed()) {
783 onError(R.string.error_syntax);
784 } else {
785 mEvaluator.requireResult();
786 }
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700787 }
788 }
789
790 private void onDelete() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700791 // Delete works like backspace; remove the last character or operator from the expression.
792 // Note that we handle keyboard delete exactly like the delete button. For
793 // example the delete button can be used to delete a character from an incomplete
794 // function name typed on a physical keyboard.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700795 // This should be impossible in RESULT state.
Hans Boehmc1ea0912015-06-19 15:05:07 -0700796 // If there is an in-progress explicit evaluation, just cancel it and return.
797 if (cancelIfEvaluating(false)) return;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700798 setState(CalculatorState.INPUT);
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800799 if (haveUnprocessed()) {
800 mUnprocessedChars = mUnprocessedChars.substring(0, mUnprocessedChars.length() - 1);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700801 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700802 mEvaluator.delete();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700803 }
Hans Boehmaf04c3a2016-01-27 14:50:08 -0800804 if (mEvaluator.getExpr().isEmpty() && !haveUnprocessed()) {
Hans Boehmdb6f9992015-08-19 12:32:56 -0700805 // Resulting formula won't be announced, since it's empty.
806 announceClearedForAccessibility();
807 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700808 redisplayAfterFormulaChange();
Budi Kusmiantoroad8e88a2014-08-11 17:21:09 -0700809 }
810
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700811 private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
Justin Klaassen06360f92014-08-28 11:08:44 -0700812 final ViewGroupOverlay groupOverlay =
813 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
Justin Klaassen8fff1442014-06-19 10:43:29 -0700814
815 final Rect displayRect = new Rect();
Justin Klaassen06360f92014-08-28 11:08:44 -0700816 mDisplayView.getGlobalVisibleRect(displayRect);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700817
818 // Make reveal cover the display and status bar.
819 final View revealView = new View(this);
Justin Klaassen8fff1442014-06-19 10:43:29 -0700820 revealView.setBottom(displayRect.bottom);
821 revealView.setLeft(displayRect.left);
822 revealView.setRight(displayRect.right);
Chenjie Yu3937b652016-06-01 23:14:26 -0700823 revealView.setBackgroundColor(ContextCompat.getColor(this, colorRes));
Justin Klaassen06360f92014-08-28 11:08:44 -0700824 groupOverlay.add(revealView);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700825
Justin Klaassen4b3af052014-05-27 17:53:10 -0700826 final int[] clearLocation = new int[2];
827 sourceView.getLocationInWindow(clearLocation);
828 clearLocation[0] += sourceView.getWidth() / 2;
829 clearLocation[1] += sourceView.getHeight() / 2;
830
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700831 final int revealCenterX = clearLocation[0] - revealView.getLeft();
832 final int revealCenterY = clearLocation[1] - revealView.getTop();
Justin Klaassen4b3af052014-05-27 17:53:10 -0700833
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700834 final double x1_2 = Math.pow(revealView.getLeft() - revealCenterX, 2);
835 final double x2_2 = Math.pow(revealView.getRight() - revealCenterX, 2);
836 final double y_2 = Math.pow(revealView.getTop() - revealCenterY, 2);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700837 final float revealRadius = (float) Math.max(Math.sqrt(x1_2 + y_2), Math.sqrt(x2_2 + y_2));
838
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700839 final Animator revealAnimator =
840 ViewAnimationUtils.createCircularReveal(revealView,
ztenghui3d6ecaf2014-06-05 09:56:00 -0700841 revealCenterX, revealCenterY, 0.0f, revealRadius);
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700842 revealAnimator.setDuration(
Justin Klaassen4b3af052014-05-27 17:53:10 -0700843 getResources().getInteger(android.R.integer.config_longAnimTime));
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700844 revealAnimator.addListener(listener);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700845
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700846 final Animator alphaAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700847 alphaAnimator.setDuration(
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700848 getResources().getInteger(android.R.integer.config_mediumAnimTime));
Justin Klaassen4b3af052014-05-27 17:53:10 -0700849
850 final AnimatorSet animatorSet = new AnimatorSet();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700851 animatorSet.play(revealAnimator).before(alphaAnimator);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700852 animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
853 animatorSet.addListener(new AnimatorListenerAdapter() {
854 @Override
Justin Klaassen4b3af052014-05-27 17:53:10 -0700855 public void onAnimationEnd(Animator animator) {
Justin Klaassen8fff1442014-06-19 10:43:29 -0700856 groupOverlay.remove(revealView);
Justin Klaassen4b3af052014-05-27 17:53:10 -0700857 mCurrentAnimator = null;
858 }
859 });
860
861 mCurrentAnimator = animatorSet;
862 animatorSet.start();
863 }
864
Hans Boehmdb6f9992015-08-19 12:32:56 -0700865 private void announceClearedForAccessibility() {
866 mResultText.announceForAccessibility(getResources().getString(R.string.cleared));
Hans Boehmccc55662015-07-07 14:16:59 -0700867 }
868
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700869 private void onClear() {
Justin Klaassen1a428cf2016-02-24 15:58:18 -0800870 if (mEvaluator.getExpr().isEmpty() && !haveUnprocessed()) {
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700871 return;
872 }
Hans Boehmc1ea0912015-06-19 15:05:07 -0700873 cancelIfEvaluating(true);
Hans Boehmdb6f9992015-08-19 12:32:56 -0700874 announceClearedForAccessibility();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700875 reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
876 @Override
877 public void onAnimationEnd(Animator animation) {
Hans Boehm760a9dc2015-04-20 10:27:12 -0700878 mUnprocessedChars = null;
Justin Klaassen44595162015-05-28 17:55:20 -0700879 mResultText.clear();
Hans Boehm760a9dc2015-04-20 10:27:12 -0700880 mEvaluator.clear();
881 setState(CalculatorState.INPUT);
Hans Boehm52d477a2016-04-01 17:42:50 -0700882 showOrHideToolbar();
Hans Boehm84614952014-11-25 18:46:17 -0800883 redisplayFormula();
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700884 }
885 });
886 }
887
Hans Boehm84614952014-11-25 18:46:17 -0800888 // Evaluation encountered en error. Display the error.
889 void onError(final int errorResourceId) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700890 if (mCurrentState == CalculatorState.EVALUATE) {
891 setState(CalculatorState.ANIMATE);
Hans Boehmccc55662015-07-07 14:16:59 -0700892 mResultText.announceForAccessibility(getResources().getString(errorResourceId));
Hans Boehmfbcef702015-04-27 18:07:47 -0700893 reveal(mCurrentButton, R.color.calculator_error_color,
894 new AnimatorListenerAdapter() {
895 @Override
896 public void onAnimationEnd(Animator animation) {
897 setState(CalculatorState.ERROR);
Justin Klaassen44595162015-05-28 17:55:20 -0700898 mResultText.displayError(errorResourceId);
Hans Boehmfbcef702015-04-27 18:07:47 -0700899 }
900 });
901 } else if (mCurrentState == CalculatorState.INIT) {
902 setState(CalculatorState.ERROR);
Justin Klaassen44595162015-05-28 17:55:20 -0700903 mResultText.displayError(errorResourceId);
Hans Boehmc023b732015-04-29 11:30:47 -0700904 } else {
Justin Klaassen44595162015-05-28 17:55:20 -0700905 mResultText.clear();
Justin Klaassen2be4fdb2014-08-06 19:54:09 -0700906 }
Justin Klaassen5f2a3342014-06-11 17:40:22 -0700907 }
908
Hans Boehm84614952014-11-25 18:46:17 -0800909 // Animate movement of result into the top formula slot.
910 // Result window now remains translated in the top slot while the result is displayed.
911 // (We convert it back to formula use only when the user provides new input.)
Justin Klaassen44595162015-05-28 17:55:20 -0700912 // Historical note: In the Lollipop version, this invisibly and instantaneously moved
Hans Boehm84614952014-11-25 18:46:17 -0800913 // formula and result displays back at the end of the animation. We no longer do that,
914 // so that we can continue to properly support scrolling of the result.
915 // We assume the result already contains the text to be expanded.
916 private void onResult(boolean animate) {
Justin Klaassen44595162015-05-28 17:55:20 -0700917 // Calculate the textSize that would be used to display the result in the formula.
918 // For scrollable results just use the minimum textSize to maximize the number of digits
919 // that are visible on screen.
920 float textSize = mFormulaText.getMinimumTextSize();
921 if (!mResultText.isScrollable()) {
922 textSize = mFormulaText.getVariableTextSize(mResultText.getText().toString());
923 }
924
925 // Scale the result to match the calculated textSize, minimizing the jump-cut transition
926 // when a result is reused in a subsequent expression.
927 final float resultScale = textSize / mResultText.getTextSize();
928
929 // Set the result's pivot to match its gravity.
930 mResultText.setPivotX(mResultText.getWidth() - mResultText.getPaddingRight());
931 mResultText.setPivotY(mResultText.getHeight() - mResultText.getPaddingBottom());
932
933 // Calculate the necessary translations so the result takes the place of the formula and
934 // the formula moves off the top of the screen.
Annie Chin28589dc2016-06-09 17:50:51 -0700935 final float resultTranslationY = (mFormulaContainer.getBottom() - mResultText.getBottom())
936 - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom());
937 float formulaTranslationY = -mFormulaContainer.getBottom();
Annie Chin26e159e2016-05-18 15:17:14 -0700938 if (mOneLine) {
939 // Position the result text.
940 mResultText.setY(mResultText.getBottom());
Annie Chin28589dc2016-06-09 17:50:51 -0700941 formulaTranslationY = -(findViewById(R.id.toolbar).getBottom()
942 + mFormulaContainer.getBottom());
Annie Chin26e159e2016-05-18 15:17:14 -0700943 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700944
Justin Klaassen44595162015-05-28 17:55:20 -0700945 // Change the result's textColor to match the formula.
946 final int formulaTextColor = mFormulaText.getCurrentTextColor();
947
Hans Boehm84614952014-11-25 18:46:17 -0800948 if (animate) {
Hans Boehmccc55662015-07-07 14:16:59 -0700949 mResultText.announceForAccessibility(getResources().getString(R.string.desc_eq));
950 mResultText.announceForAccessibility(mResultText.getText());
Hans Boehmc1ea0912015-06-19 15:05:07 -0700951 setState(CalculatorState.ANIMATE);
Hans Boehm84614952014-11-25 18:46:17 -0800952 final AnimatorSet animatorSet = new AnimatorSet();
953 animatorSet.playTogether(
Justin Klaassen44595162015-05-28 17:55:20 -0700954 ObjectAnimator.ofPropertyValuesHolder(mResultText,
955 PropertyValuesHolder.ofFloat(View.SCALE_X, resultScale),
956 PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale),
957 PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)),
958 ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor),
Annie Chine918fd22016-03-09 11:07:54 -0800959 ObjectAnimator.ofFloat(mFormulaContainer, View.TRANSLATION_Y,
960 formulaTranslationY));
Justin Klaassen44595162015-05-28 17:55:20 -0700961 animatorSet.setDuration(getResources().getInteger(
962 android.R.integer.config_longAnimTime));
Hans Boehm84614952014-11-25 18:46:17 -0800963 animatorSet.addListener(new AnimatorListenerAdapter() {
964 @Override
Hans Boehm84614952014-11-25 18:46:17 -0800965 public void onAnimationEnd(Animator animation) {
966 setState(CalculatorState.RESULT);
967 mCurrentAnimator = null;
968 }
969 });
Justin Klaassen4b3af052014-05-27 17:53:10 -0700970
Hans Boehm84614952014-11-25 18:46:17 -0800971 mCurrentAnimator = animatorSet;
972 animatorSet.start();
973 } else /* No animation desired; get there fast, e.g. when restarting */ {
Justin Klaassen44595162015-05-28 17:55:20 -0700974 mResultText.setScaleX(resultScale);
975 mResultText.setScaleY(resultScale);
976 mResultText.setTranslationY(resultTranslationY);
977 mResultText.setTextColor(formulaTextColor);
Annie Chine918fd22016-03-09 11:07:54 -0800978 mFormulaContainer.setTranslationY(formulaTranslationY);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700979 setState(CalculatorState.RESULT);
Hans Boehm84614952014-11-25 18:46:17 -0800980 }
Justin Klaassen4b3af052014-05-27 17:53:10 -0700981 }
Hans Boehm84614952014-11-25 18:46:17 -0800982
983 // Restore positions of the formula and result displays back to their original,
984 // pre-animation state.
985 private void restoreDisplayPositions() {
986 // Clear result.
Justin Klaassen44595162015-05-28 17:55:20 -0700987 mResultText.setText("");
Hans Boehm84614952014-11-25 18:46:17 -0800988 // Reset all of the values modified during the animation.
Justin Klaassen44595162015-05-28 17:55:20 -0700989 mResultText.setScaleX(1.0f);
990 mResultText.setScaleY(1.0f);
991 mResultText.setTranslationX(0.0f);
992 mResultText.setTranslationY(0.0f);
Annie Chine918fd22016-03-09 11:07:54 -0800993 mFormulaContainer.setTranslationY(0.0f);
Hans Boehm84614952014-11-25 18:46:17 -0800994
Hans Boehm08e8f322015-04-21 13:18:38 -0700995 mFormulaText.requestFocus();
Hans Boehm5e6a0ca2015-09-22 17:09:01 -0700996 }
997
998 @Override
999 public void onClick(AlertDialogFragment fragment, int which) {
1000 if (which == DialogInterface.BUTTON_POSITIVE) {
1001 // Timeout extension request.
1002 mEvaluator.setLongTimeOut();
1003 }
1004 }
Hans Boehm84614952014-11-25 18:46:17 -08001005
Justin Klaassend48b7562015-04-16 16:51:38 -07001006 @Override
1007 public boolean onCreateOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -07001008 super.onCreateOptionsMenu(menu);
1009
1010 getMenuInflater().inflate(R.menu.activity_calculator, menu);
Justin Klaassend48b7562015-04-16 16:51:38 -07001011 return true;
1012 }
1013
1014 @Override
1015 public boolean onPrepareOptionsMenu(Menu menu) {
Justin Klaassend36d63e2015-05-05 12:59:36 -07001016 super.onPrepareOptionsMenu(menu);
1017
1018 // Show the leading option when displaying a result.
1019 menu.findItem(R.id.menu_leading).setVisible(mCurrentState == CalculatorState.RESULT);
1020
1021 // Show the fraction option when displaying a rational result.
1022 menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT
Hans Boehm995e5eb2016-02-08 11:03:01 -08001023 && mEvaluator.getResult().exactlyDisplayable());
Justin Klaassend36d63e2015-05-05 12:59:36 -07001024
Justin Klaassend48b7562015-04-16 16:51:38 -07001025 return true;
Hans Boehm84614952014-11-25 18:46:17 -08001026 }
1027
1028 @Override
Justin Klaassend48b7562015-04-16 16:51:38 -07001029 public boolean onOptionsItemSelected(MenuItem item) {
Hans Boehm84614952014-11-25 18:46:17 -08001030 switch (item.getItemId()) {
Justin Klaassend36d63e2015-05-05 12:59:36 -07001031 case R.id.menu_leading:
1032 displayFull();
Hans Boehm84614952014-11-25 18:46:17 -08001033 return true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001034 case R.id.menu_fraction:
1035 displayFraction();
1036 return true;
Justin Klaassend36d63e2015-05-05 12:59:36 -07001037 case R.id.menu_licenses:
1038 startActivity(new Intent(this, Licenses.class));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001039 return true;
Hans Boehm84614952014-11-25 18:46:17 -08001040 default:
1041 return super.onOptionsItemSelected(item);
1042 }
1043 }
1044
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001045 private void displayMessage(String s) {
Hans Boehm5e6a0ca2015-09-22 17:09:01 -07001046 AlertDialogFragment.showMessageDialog(this, s, null);
Hans Boehm84614952014-11-25 18:46:17 -08001047 }
1048
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001049 private void displayFraction() {
Hans Boehm995e5eb2016-02-08 11:03:01 -08001050 UnifiedReal result = mEvaluator.getResult();
Hans Boehm013969e2015-04-13 20:29:47 -07001051 displayMessage(KeyMaps.translateResult(result.toNiceString()));
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001052 }
1053
1054 // Display full result to currently evaluated precision
1055 private void displayFull() {
1056 Resources res = getResources();
Hans Boehm24c91ed2016-06-30 18:53:44 -07001057 String msg = mResultText.getFullText(true /* withSeparators */) + " ";
Justin Klaassen44595162015-05-28 17:55:20 -07001058 if (mResultText.fullTextIsExact()) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001059 msg += res.getString(R.string.exact);
1060 } else {
1061 msg += res.getString(R.string.approximate);
1062 }
1063 displayMessage(msg);
1064 }
1065
Hans Boehm017de982015-06-10 17:46:03 -07001066 /**
1067 * Add input characters to the end of the expression.
1068 * Map them to the appropriate button pushes when possible. Leftover characters
1069 * are added to mUnprocessedChars, which is presumed to immediately precede the newly
1070 * added characters.
Hans Boehm65a99a42016-02-03 18:16:07 -08001071 * @param moreChars characters to be added
1072 * @param explicit these characters were explicitly typed by the user, not pasted
Hans Boehm017de982015-06-10 17:46:03 -07001073 */
1074 private void addChars(String moreChars, boolean explicit) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001075 if (mUnprocessedChars != null) {
1076 moreChars = mUnprocessedChars + moreChars;
1077 }
1078 int current = 0;
1079 int len = moreChars.length();
Hans Boehm0b9806f2015-06-29 16:07:15 -07001080 boolean lastWasDigit = false;
Hans Boehm5d79d102015-09-16 16:33:47 -07001081 if (mCurrentState == CalculatorState.RESULT && len != 0) {
1082 // Clear display immediately for incomplete function name.
1083 switchToInput(KeyMaps.keyForChar(moreChars.charAt(current)));
1084 }
Hans Boehm24c91ed2016-06-30 18:53:44 -07001085 char groupingSeparator = KeyMaps.translateResult(",").charAt(0);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001086 while (current < len) {
1087 char c = moreChars.charAt(current);
Hans Boehm24c91ed2016-06-30 18:53:44 -07001088 if (Character.isSpaceChar(c) || c == groupingSeparator) {
1089 ++current;
1090 continue;
1091 }
Hans Boehm013969e2015-04-13 20:29:47 -07001092 int k = KeyMaps.keyForChar(c);
Hans Boehm0b9806f2015-06-29 16:07:15 -07001093 if (!explicit) {
1094 int expEnd;
1095 if (lastWasDigit && current !=
1096 (expEnd = Evaluator.exponentEnd(moreChars, current))) {
1097 // Process scientific notation with 'E' when pasting, in spite of ambiguity
1098 // with base of natural log.
1099 // Otherwise the 10^x key is the user's friend.
1100 mEvaluator.addExponent(moreChars, current, expEnd);
1101 current = expEnd;
1102 lastWasDigit = false;
1103 continue;
1104 } else {
1105 boolean isDigit = KeyMaps.digVal(k) != KeyMaps.NOT_DIGIT;
1106 if (current == 0 && (isDigit || k == R.id.dec_point)
1107 && mEvaluator.getExpr().hasTrailingConstant()) {
1108 // Refuse to concatenate pasted content to trailing constant.
1109 // This makes pasting of calculator results more consistent, whether or
1110 // not the old calculator instance is still around.
1111 addKeyToExpr(R.id.op_mul);
1112 }
1113 lastWasDigit = (isDigit || lastWasDigit && k == R.id.dec_point);
1114 }
1115 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001116 if (k != View.NO_ID) {
1117 mCurrentButton = findViewById(k);
Hans Boehm017de982015-06-10 17:46:03 -07001118 if (explicit) {
1119 addExplicitKeyToExpr(k);
1120 } else {
1121 addKeyToExpr(k);
1122 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001123 if (Character.isSurrogate(c)) {
1124 current += 2;
1125 } else {
1126 ++current;
1127 }
1128 continue;
1129 }
Hans Boehm013969e2015-04-13 20:29:47 -07001130 int f = KeyMaps.funForString(moreChars, current);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001131 if (f != View.NO_ID) {
1132 mCurrentButton = findViewById(f);
Hans Boehm017de982015-06-10 17:46:03 -07001133 if (explicit) {
1134 addExplicitKeyToExpr(f);
1135 } else {
1136 addKeyToExpr(f);
1137 }
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001138 if (f == R.id.op_sqrt) {
1139 // Square root entered as function; don't lose the parenthesis.
1140 addKeyToExpr(R.id.lparen);
1141 }
1142 current = moreChars.indexOf('(', current) + 1;
1143 continue;
1144 }
1145 // There are characters left, but we can't convert them to button presses.
1146 mUnprocessedChars = moreChars.substring(current);
1147 redisplayAfterFormulaChange();
Hans Boehm52d477a2016-04-01 17:42:50 -07001148 showOrHideToolbar();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001149 return;
1150 }
1151 mUnprocessedChars = null;
1152 redisplayAfterFormulaChange();
Hans Boehm52d477a2016-04-01 17:42:50 -07001153 showOrHideToolbar();
Hans Boehm84614952014-11-25 18:46:17 -08001154 }
1155
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001156 @Override
Justin Klaassenfc5ac822015-06-18 13:15:17 -07001157 public boolean onPaste(ClipData clip) {
1158 final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0);
1159 if (item == null) {
1160 // nothing to paste, bail early...
1161 return false;
1162 }
1163
1164 // Check if the item is a previously copied result, otherwise paste as raw text.
1165 final Uri uri = item.getUri();
1166 if (uri != null && mEvaluator.isLastSaved(uri)) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001167 if (mCurrentState == CalculatorState.ERROR
Justin Klaassenfc5ac822015-06-18 13:15:17 -07001168 || mCurrentState == CalculatorState.RESULT) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001169 setState(CalculatorState.INPUT);
1170 mEvaluator.clear();
Hans Boehm84614952014-11-25 18:46:17 -08001171 }
Hans Boehm3666e632015-07-27 18:33:12 -07001172 mEvaluator.appendSaved();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -07001173 redisplayAfterFormulaChange();
Justin Klaassenfc5ac822015-06-18 13:15:17 -07001174 } else {
Hans Boehm017de982015-06-10 17:46:03 -07001175 addChars(item.coerceToText(this).toString(), false);
Hans Boehm84614952014-11-25 18:46:17 -08001176 }
Justin Klaassenfc5ac822015-06-18 13:15:17 -07001177 return true;
Hans Boehm84614952014-11-25 18:46:17 -08001178 }
Chenjie Yu3937b652016-06-01 23:14:26 -07001179
1180 /**
1181 * Clean up animation for context menu.
1182 */
1183 @Override
1184 public void onContextMenuClosed(Menu menu) {
1185 stopActionModeOrContextMenu();
1186 }
Justin Klaassen4b3af052014-05-27 17:53:10 -07001187}