Blanket copy of PhoneApp to services/Telephony.

First phase of splitting out InCallUI from PhoneApp.

Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/InCallUiState.java b/src/com/android/phone/InCallUiState.java
new file mode 100644
index 0000000..3b700d7
--- /dev/null
+++ b/src/com/android/phone/InCallUiState.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.android.phone.Constants.CallStatusCode;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+
+/**
+ * Helper class to keep track of "persistent state" of the in-call UI.
+ *
+ * The onscreen appearance of the in-call UI mostly depends on the current
+ * Call/Connection state, which is owned by the telephony framework.  But
+ * there's some application-level "UI state" too, which lives here in the
+ * phone app.
+ *
+ * This application-level state information is *not* maintained by the
+ * InCallScreen, since it needs to persist throughout an entire phone call,
+ * not just a single resume/pause cycle of the InCallScreen.  So instead, that
+ * state is stored here, in a singleton instance of this class.
+ *
+ * The state kept here is a high-level abstraction of in-call UI state: we
+ * don't know about implementation details like specific widgets or strings or
+ * resources, but we do understand higher level concepts (for example "is the
+ * dialpad visible") and high-level modes (like InCallScreenMode) and error
+ * conditions (like CallStatusCode).
+ *
+ * @see InCallControlState for a separate collection of "UI state" that
+ * controls all the onscreen buttons of the in-call UI, based on the state of
+ * the telephony layer.
+ *
+ * The singleton instance of this class is owned by the PhoneApp instance.
+ */
+public class InCallUiState {
+    private static final String TAG = "InCallUiState";
+    private static final boolean DBG = false;
+
+    /** The singleton InCallUiState instance. */
+    private static InCallUiState sInstance;
+
+    private Context mContext;
+
+    /**
+     * Initialize the singleton InCallUiState instance.
+     *
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     * From then on, the InCallUiState instance is available via the
+     * PhoneApp's public "inCallUiState" field, which is why there's no
+     * getInstance() method here.
+     */
+    /* package */ static InCallUiState init(Context context) {
+        synchronized (InCallUiState.class) {
+            if (sInstance == null) {
+                sInstance = new InCallUiState(context);
+            } else {
+                Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Private constructor (this is a singleton).
+     * @see init()
+     */
+    private InCallUiState(Context context) {
+        mContext = context;
+    }
+
+
+    //
+    // (1) High-level state of the whole in-call UI
+    //
+
+    /** High-level "modes" of the in-call UI. */
+    public enum InCallScreenMode {
+        /**
+         * Normal in-call UI elements visible.
+         */
+        NORMAL,
+        /**
+         * "Manage conference" UI is visible, totally replacing the
+         * normal in-call UI.
+         */
+        MANAGE_CONFERENCE,
+        /**
+         * Non-interactive UI state.  Call card is visible,
+         * displaying information about the call that just ended.
+         */
+        CALL_ENDED,
+        /**
+         * Normal OTA in-call UI elements visible.
+         */
+        OTA_NORMAL,
+        /**
+         * OTA call ended UI visible, replacing normal OTA in-call UI.
+         */
+        OTA_ENDED,
+        /**
+         * Default state when not on call
+         */
+        UNDEFINED
+    }
+
+    /** Current high-level "mode" of the in-call UI. */
+    InCallScreenMode inCallScreenMode = InCallScreenMode.UNDEFINED;
+
+
+    //
+    // (2) State of specific UI elements
+    //
+
+    /**
+     * Is the onscreen twelve-key dialpad visible?
+     */
+    boolean showDialpad;
+
+    /**
+     * The contents of the twelve-key dialpad's "digits" display, which is
+     * visible only when the dialpad itself is visible.
+     *
+     * (This is basically the "history" of DTMF digits you've typed so far
+     * in the current call.  It's cleared out any time a new call starts,
+     * to make sure the digits don't persist between two separate calls.)
+     */
+    String dialpadDigits;
+
+    /**
+     * The contact/dialed number information shown in the DTMF digits text
+     * when the user has not yet typed any digits.
+     *
+     * Currently only used for displaying "Voice Mail" since voicemail calls
+     * start directly in the dialpad view.
+     */
+    String dialpadContextText;
+
+    //
+    // (3) Error / diagnostic indications
+    //
+
+    // This section provides an abstract concept of an "error status
+    // indication" for some kind of exceptional condition that needs to be
+    // communicated to the user, in the context of the in-call UI.
+    //
+    // If mPendingCallStatusCode is any value other than SUCCESS, that
+    // indicates that the in-call UI needs to display a dialog to the user
+    // with the specified title and message text.
+    //
+    // When an error occurs outside of the InCallScreen itself (like
+    // during CallController.placeCall() for example), we inform the user
+    // by doing the following steps:
+    //
+    // (1) set the "pending call status code" to a value other than SUCCESS
+    //     (based on the specific error that happened)
+    // (2) force the InCallScreen to be launched (or relaunched)
+    // (3) InCallScreen.onResume() will notice that pending call status code
+    //     is set, and will actually bring up the desired dialog.
+    //
+    // Watch out: any time you set (or change!) the pending call status code
+    // field you must be sure to always (re)launch the InCallScreen.
+    //
+    // Finally, the InCallScreen itself is responsible for resetting the
+    // pending call status code, when the user dismisses the dialog (like by
+    // hitting the OK button or pressing Back).  The pending call status code
+    // field is NOT cleared simply by the InCallScreen being paused or
+    // finished, since the resulting dialog needs to persist across
+    // orientation changes or if the screen turns off.
+
+    // TODO: other features we might eventually need here:
+    //
+    //   - Some error status messages stay in force till reset,
+    //     others may automatically clear themselves after
+    //     a fixed delay
+    //
+    //   - Some error statuses may be visible as a dialog with an OK
+    //     button (like "call failed"), others may be an indefinite
+    //     progress dialog (like "turning on radio for emergency call").
+    //
+    //   - Eventually some error statuses may have extra actions (like a
+    //     "retry call" button that we might provide at the bottom of the
+    //     "call failed because you have no signal" dialog.)
+
+    /**
+     * The current pending "error status indication" that we need to
+     * display to the user.
+     *
+     * If this field is set to a value other than SUCCESS, this indicates to
+     * the InCallScreen that we need to show some kind of message to the user
+     * (usually an error dialog) based on the specified status code.
+     */
+    private CallStatusCode mPendingCallStatusCode = CallStatusCode.SUCCESS;
+
+    /**
+     * @return true if there's a pending "error status indication"
+     * that we need to display to the user.
+     */
+    public boolean hasPendingCallStatusCode() {
+        if (DBG) log("hasPendingCallStatusCode() ==> "
+                     + (mPendingCallStatusCode != CallStatusCode.SUCCESS));
+        return (mPendingCallStatusCode != CallStatusCode.SUCCESS);
+    }
+
+    /**
+     * @return the pending "error status indication" code
+     * that we need to display to the user.
+     */
+    public CallStatusCode getPendingCallStatusCode() {
+        if (DBG) log("getPendingCallStatusCode() ==> " + mPendingCallStatusCode);
+        return mPendingCallStatusCode;
+    }
+
+    /**
+     * Sets the pending "error status indication" code.
+     */
+    public void setPendingCallStatusCode(CallStatusCode status) {
+        if (DBG) log("setPendingCallStatusCode( " + status + " )...");
+        if (mPendingCallStatusCode != CallStatusCode.SUCCESS) {
+            // Uh oh: mPendingCallStatusCode is already set to some value
+            // other than SUCCESS (which indicates that there was some kind of
+            // failure), and now we're trying to indicate another (potentially
+            // different) failure.  But we can only indicate one failure at a
+            // time to the user, so the previous pending code is now going to
+            // be lost.
+            Log.w(TAG, "setPendingCallStatusCode: setting new code " + status
+                  + ", but a previous code " + mPendingCallStatusCode
+                  + " was already pending!");
+        }
+        mPendingCallStatusCode = status;
+    }
+
+    /**
+     * Clears out the pending "error status indication" code.
+     *
+     * This indicates that there's no longer any error or "exceptional
+     * condition" that needs to be displayed to the user.  (Typically, this
+     * method is called when the user dismisses the error dialog that came up
+     * because of a previous call status code.)
+     */
+    public void clearPendingCallStatusCode() {
+        if (DBG) log("clearPendingCallStatusCode()...");
+        mPendingCallStatusCode = CallStatusCode.SUCCESS;
+    }
+
+    /**
+     * Flag used to control the CDMA-specific "call lost" dialog.
+     *
+     * If true, that means that if the *next* outgoing call fails with an
+     * abnormal disconnection cause, we need to display the "call lost"
+     * dialog.  (Normally, in CDMA we handle some types of call failures
+     * by automatically retrying the call.  This flag is set to true when
+     * we're about to auto-retry, which means that if the *retry* also
+     * fails we'll give up and display an error.)
+     * See the logic in InCallScreen.onDisconnect() for the full story.
+     *
+     * TODO: the state machine that maintains the needToShowCallLostDialog
+     * flag in InCallScreen.onDisconnect() should really be moved into the
+     * CallController.  Then we can get rid of this extra flag, and
+     * instead simply use the CallStatusCode value CDMA_CALL_LOST to
+     * trigger the "call lost" dialog.
+     */
+    boolean needToShowCallLostDialog;
+
+
+    //
+    // Progress indications
+    //
+
+    /**
+     * Possible messages we might need to display along with
+     * an indefinite progress spinner.
+     */
+    public enum ProgressIndicationType {
+        /**
+         * No progress indication needs to be shown.
+         */
+        NONE,
+
+        /**
+         * Shown when making an emergency call from airplane mode;
+         * see CallController$EmergencyCallHelper.
+         */
+        TURNING_ON_RADIO,
+
+        /**
+         * Generic "retrying" state.  (Specifically, this is shown while
+         * retrying after an initial failure from the "emergency call from
+         * airplane mode" sequence.)
+         */
+         RETRYING
+    }
+
+    /**
+     * The current progress indication that should be shown
+     * to the user.  Any value other than NONE will cause the InCallScreen
+     * to bring up an indefinite progress spinner along with a message
+     * corresponding to the specified ProgressIndicationType.
+     */
+    private ProgressIndicationType progressIndication = ProgressIndicationType.NONE;
+
+    /** Sets the current progressIndication. */
+    public void setProgressIndication(ProgressIndicationType value) {
+        progressIndication = value;
+    }
+
+    /** Clears the current progressIndication. */
+    public void clearProgressIndication() {
+        progressIndication = ProgressIndicationType.NONE;
+    }
+
+    /**
+     * @return the current progress indication type, or ProgressIndicationType.NONE
+     * if no progress indication is currently active.
+     */
+    public ProgressIndicationType getProgressIndication() {
+        return progressIndication;
+    }
+
+    /** @return true if a progress indication is currently active. */
+    public boolean isProgressIndicationActive() {
+        return (progressIndication != ProgressIndicationType.NONE);
+    }
+
+
+    //
+    // (4) Optional info when a 3rd party "provider" is used.
+    //     @see InCallScreen#requestRemoveProviderInfoWithDelay()
+    //     @see CallCard#updateCallStateWidgets()
+    //
+
+    // TODO: maybe isolate all the provider-related stuff out to a
+    //       separate inner class?
+    boolean providerInfoVisible;
+    CharSequence providerLabel;
+    Drawable providerIcon;
+    Uri providerGatewayUri;
+    // The formatted address extracted from mProviderGatewayUri. User visible.
+    String providerAddress;
+
+    /**
+     * Set the fields related to the provider support
+     * based on the specified intent.
+     */
+    public void setProviderInfo(Intent intent) {
+        providerLabel = PhoneUtils.getProviderLabel(mContext, intent);
+        providerIcon = PhoneUtils.getProviderIcon(mContext, intent);
+        providerGatewayUri = PhoneUtils.getProviderGatewayUri(intent);
+        providerAddress = PhoneUtils.formatProviderUri(providerGatewayUri);
+        providerInfoVisible = true;
+
+        // ...but if any of the "required" fields are missing, completely
+        // disable the overlay.
+        if (TextUtils.isEmpty(providerLabel) || providerIcon == null ||
+            providerGatewayUri == null || TextUtils.isEmpty(providerAddress)) {
+            clearProviderInfo();
+        }
+    }
+
+    /**
+     * Clear all the fields related to the provider support.
+     */
+    public void clearProviderInfo() {
+        providerInfoVisible = false;
+        providerLabel = null;
+        providerIcon = null;
+        providerGatewayUri = null;
+        providerAddress = null;
+    }
+
+    /**
+     * "Call origin" of the most recent phone call.
+     *
+     * Watch out: right now this is only used to determine where the user should go after the phone
+     * call. See also {@link InCallScreen} for more detail. There is *no* specific specification
+     * about how this variable will be used.
+     *
+     * @see PhoneGlobals#setLatestActiveCallOrigin(String)
+     * @see PhoneGlobals#createPhoneEndIntentUsingCallOrigin()
+     *
+     * TODO: we should determine some public behavior for this variable.
+     */
+    String latestActiveCallOrigin;
+
+    /**
+     * Timestamp for "Call origin". This will be used to preserve when the call origin was set.
+     * {@link android.os.SystemClock#elapsedRealtime()} will be used.
+     */
+    long latestActiveCallOriginTimeStamp;
+
+    /**
+     * Flag forcing Phone app to show in-call UI even when there's no phone call and thus Phone
+     * is in IDLE state. This will be turned on only when:
+     *
+     * - the last phone call is hung up, and
+     * - the screen is being turned off in the middle of in-call UI (and thus when the screen being
+     *   turned on in-call UI is expected to be the next foreground activity)
+     *
+     * At that moment whole UI should show "previously disconnected phone call" for a moment and
+     * exit itself. {@link InCallScreen#onPause()} will turn this off and prevent possible weird
+     * cases which may happen with that exceptional case.
+     */
+    boolean showAlreadyDisconnectedState;
+
+    //
+    // Debugging
+    //
+
+    public void dumpState() {
+        log("dumpState():");
+        log("  - showDialpad: " + showDialpad);
+        log("    - dialpadContextText: " + dialpadContextText);
+        if (hasPendingCallStatusCode()) {
+            log("  - status indication is pending!");
+            log("    - pending call status code = " + mPendingCallStatusCode);
+        } else {
+            log("  - pending call status code: none");
+        }
+        log("  - progressIndication: " + progressIndication);
+        if (providerInfoVisible) {
+            log("  - provider info VISIBLE: "
+                  + providerLabel + " / "
+                  + providerIcon  + " / "
+                  + providerGatewayUri + " / "
+                  + providerAddress);
+        } else {
+            log("  - provider info: none");
+        }
+        log("  - latestActiveCallOrigin: " + latestActiveCallOrigin);
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}