Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      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 
     17 package com.android.phone;
     18 
     19 import com.android.phone.Constants.CallStatusCode;
     20 
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.graphics.drawable.Drawable;
     24 import android.net.Uri;
     25 import android.text.TextUtils;
     26 import android.util.Log;
     27 
     28 
     29 /**
     30  * Helper class to keep track of "persistent state" of the in-call UI.
     31  *
     32  * The onscreen appearance of the in-call UI mostly depends on the current
     33  * Call/Connection state, which is owned by the telephony framework.  But
     34  * there's some application-level "UI state" too, which lives here in the
     35  * phone app.
     36  *
     37  * This application-level state information is *not* maintained by the
     38  * InCallScreen, since it needs to persist throughout an entire phone call,
     39  * not just a single resume/pause cycle of the InCallScreen.  So instead, that
     40  * state is stored here, in a singleton instance of this class.
     41  *
     42  * The state kept here is a high-level abstraction of in-call UI state: we
     43  * don't know about implementation details like specific widgets or strings or
     44  * resources, but we do understand higher level concepts (for example "is the
     45  * dialpad visible") and high-level modes (like InCallScreenMode) and error
     46  * conditions (like CallStatusCode).
     47  *
     48  * @see InCallControlState for a separate collection of "UI state" that
     49  * controls all the onscreen buttons of the in-call UI, based on the state of
     50  * the telephony layer.
     51  *
     52  * The singleton instance of this class is owned by the PhoneApp instance.
     53  */
     54 public class InCallUiState {
     55     private static final String TAG = "InCallUiState";
     56     private static final boolean DBG = false;
     57 
     58     /** The singleton InCallUiState instance. */
     59     private static InCallUiState sInstance;
     60 
     61     private Context mContext;
     62 
     63     /**
     64      * Initialize the singleton InCallUiState instance.
     65      *
     66      * This is only done once, at startup, from PhoneApp.onCreate().
     67      * From then on, the InCallUiState instance is available via the
     68      * PhoneApp's public "inCallUiState" field, which is why there's no
     69      * getInstance() method here.
     70      */
     71     /* package */ static InCallUiState init(Context context) {
     72         synchronized (InCallUiState.class) {
     73             if (sInstance == null) {
     74                 sInstance = new InCallUiState(context);
     75             } else {
     76                 Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
     77             }
     78             return sInstance;
     79         }
     80     }
     81 
     82     /**
     83      * Private constructor (this is a singleton).
     84      * @see init()
     85      */
     86     private InCallUiState(Context context) {
     87         mContext = context;
     88     }
     89 
     90 
     91     //
     92     // (1) High-level state of the whole in-call UI
     93     //
     94 
     95     /** High-level "modes" of the in-call UI. */
     96     public enum InCallScreenMode {
     97         /**
     98          * Normal in-call UI elements visible.
     99          */
    100         NORMAL,
    101         /**
    102          * "Manage conference" UI is visible, totally replacing the
    103          * normal in-call UI.
    104          */
    105         MANAGE_CONFERENCE,
    106         /**
    107          * Non-interactive UI state.  Call card is visible,
    108          * displaying information about the call that just ended.
    109          */
    110         CALL_ENDED,
    111         /**
    112          * Normal OTA in-call UI elements visible.
    113          */
    114         OTA_NORMAL,
    115         /**
    116          * OTA call ended UI visible, replacing normal OTA in-call UI.
    117          */
    118         OTA_ENDED,
    119         /**
    120          * Default state when not on call
    121          */
    122         UNDEFINED
    123     }
    124 
    125     /** Current high-level "mode" of the in-call UI. */
    126     InCallScreenMode inCallScreenMode = InCallScreenMode.UNDEFINED;
    127 
    128 
    129     //
    130     // (2) State of specific UI elements
    131     //
    132 
    133     /**
    134      * Is the onscreen twelve-key dialpad visible?
    135      */
    136     boolean showDialpad;
    137 
    138     /**
    139      * The contents of the twelve-key dialpad's "digits" display, which is
    140      * visible only when the dialpad itself is visible.
    141      *
    142      * (This is basically the "history" of DTMF digits you've typed so far
    143      * in the current call.  It's cleared out any time a new call starts,
    144      * to make sure the digits don't persist between two separate calls.)
    145      */
    146     String dialpadDigits;
    147 
    148 
    149     //
    150     // (3) Error / diagnostic indications
    151     //
    152 
    153     // This section provides an abstract concept of an "error status
    154     // indication" for some kind of exceptional condition that needs to be
    155     // communicated to the user, in the context of the in-call UI.
    156     //
    157     // If mPendingCallStatusCode is any value other than SUCCESS, that
    158     // indicates that the in-call UI needs to display a dialog to the user
    159     // with the specified title and message text.
    160     //
    161     // When an error occurs outside of the InCallScreen itself (like
    162     // during CallController.placeCall() for example), we inform the user
    163     // by doing the following steps:
    164     //
    165     // (1) set the "pending call status code" to a value other than SUCCESS
    166     //     (based on the specific error that happened)
    167     // (2) force the InCallScreen to be launched (or relaunched)
    168     // (3) InCallScreen.onResume() will notice that pending call status code
    169     //     is set, and will actually bring up the desired dialog.
    170     //
    171     // Watch out: any time you set (or change!) the pending call status code
    172     // field you must be sure to always (re)launch the InCallScreen.
    173     //
    174     // Finally, the InCallScreen itself is responsible for resetting the
    175     // pending call status code, when the user dismisses the dialog (like by
    176     // hitting the OK button or pressing Back).  The pending call status code
    177     // field is NOT cleared simply by the InCallScreen being paused or
    178     // finished, since the resulting dialog needs to persist across
    179     // orientation changes or if the screen turns off.
    180 
    181     // TODO: other features we might eventually need here:
    182     //
    183     //   - Some error status messages stay in force till reset,
    184     //     others may automatically clear themselves after
    185     //     a fixed delay
    186     //
    187     //   - Some error statuses may be visible as a dialog with an OK
    188     //     button (like "call failed"), others may be an indefinite
    189     //     progress dialog (like "turning on radio for emergency call").
    190     //
    191     //   - Eventually some error statuses may have extra actions (like a
    192     //     "retry call" button that we might provide at the bottom of the
    193     //     "call failed because you have no signal" dialog.)
    194 
    195     /**
    196      * The current pending "error status indication" that we need to
    197      * display to the user.
    198      *
    199      * If this field is set to a value other than SUCCESS, this indicates to
    200      * the InCallScreen that we need to show some kind of message to the user
    201      * (usually an error dialog) based on the specified status code.
    202      */
    203     private CallStatusCode mPendingCallStatusCode = CallStatusCode.SUCCESS;
    204 
    205     /**
    206      * @return true if there's a pending "error status indication"
    207      * that we need to display to the user.
    208      */
    209     public boolean hasPendingCallStatusCode() {
    210         if (DBG) log("hasPendingCallStatusCode() ==> "
    211                      + (mPendingCallStatusCode != CallStatusCode.SUCCESS));
    212         return (mPendingCallStatusCode != CallStatusCode.SUCCESS);
    213     }
    214 
    215     /**
    216      * @return the pending "error status indication" code
    217      * that we need to display to the user.
    218      */
    219     public CallStatusCode getPendingCallStatusCode() {
    220         if (DBG) log("getPendingCallStatusCode() ==> " + mPendingCallStatusCode);
    221         return mPendingCallStatusCode;
    222     }
    223 
    224     /**
    225      * Sets the pending "error status indication" code.
    226      */
    227     public void setPendingCallStatusCode(CallStatusCode status) {
    228         if (DBG) log("setPendingCallStatusCode( " + status + " )...");
    229         if (mPendingCallStatusCode != CallStatusCode.SUCCESS) {
    230             // Uh oh: mPendingCallStatusCode is already set to some value
    231             // other than SUCCESS (which indicates that there was some kind of
    232             // failure), and now we're trying to indicate another (potentially
    233             // different) failure.  But we can only indicate one failure at a
    234             // time to the user, so the previous pending code is now going to
    235             // be lost.
    236             Log.w(TAG, "setPendingCallStatusCode: setting new code " + status
    237                   + ", but a previous code " + mPendingCallStatusCode
    238                   + " was already pending!");
    239         }
    240         mPendingCallStatusCode = status;
    241     }
    242 
    243     /**
    244      * Clears out the pending "error status indication" code.
    245      *
    246      * This indicates that there's no longer any error or "exceptional
    247      * condition" that needs to be displayed to the user.  (Typically, this
    248      * method is called when the user dismisses the error dialog that came up
    249      * because of a previous call status code.)
    250      */
    251     public void clearPendingCallStatusCode() {
    252         if (DBG) log("clearPendingCallStatusCode()...");
    253         mPendingCallStatusCode = CallStatusCode.SUCCESS;
    254     }
    255 
    256     /**
    257      * Flag used to control the CDMA-specific "call lost" dialog.
    258      *
    259      * If true, that means that if the *next* outgoing call fails with an
    260      * abnormal disconnection cause, we need to display the "call lost"
    261      * dialog.  (Normally, in CDMA we handle some types of call failures
    262      * by automatically retrying the call.  This flag is set to true when
    263      * we're about to auto-retry, which means that if the *retry* also
    264      * fails we'll give up and display an error.)
    265      * See the logic in InCallScreen.onDisconnect() for the full story.
    266      *
    267      * TODO: the state machine that maintains the needToShowCallLostDialog
    268      * flag in InCallScreen.onDisconnect() should really be moved into the
    269      * CallController.  Then we can get rid of this extra flag, and
    270      * instead simply use the CallStatusCode value CDMA_CALL_LOST to
    271      * trigger the "call lost" dialog.
    272      */
    273     boolean needToShowCallLostDialog;
    274 
    275 
    276     //
    277     // Progress indications
    278     //
    279 
    280     /**
    281      * Possible messages we might need to display along with
    282      * an indefinite progress spinner.
    283      */
    284     public enum ProgressIndicationType {
    285         /**
    286          * No progress indication needs to be shown.
    287          */
    288         NONE,
    289 
    290         /**
    291          * Shown when making an emergency call from airplane mode;
    292          * see CallController$EmergencyCallHelper.
    293          */
    294         TURNING_ON_RADIO,
    295 
    296         /**
    297          * Generic "retrying" state.  (Specifically, this is shown while
    298          * retrying after an initial failure from the "emergency call from
    299          * airplane mode" sequence.)
    300          */
    301          RETRYING
    302     }
    303 
    304     /**
    305      * The current progress indication that should be shown
    306      * to the user.  Any value other than NONE will cause the InCallScreen
    307      * to bring up an indefinite progress spinner along with a message
    308      * corresponding to the specified ProgressIndicationType.
    309      */
    310     private ProgressIndicationType progressIndication = ProgressIndicationType.NONE;
    311 
    312     /** Sets the current progressIndication. */
    313     public void setProgressIndication(ProgressIndicationType value) {
    314         progressIndication = value;
    315     }
    316 
    317     /** Clears the current progressIndication. */
    318     public void clearProgressIndication() {
    319         progressIndication = ProgressIndicationType.NONE;
    320     }
    321 
    322     /**
    323      * @return the current progress indication type, or ProgressIndicationType.NONE
    324      * if no progress indication is currently active.
    325      */
    326     public ProgressIndicationType getProgressIndication() {
    327         return progressIndication;
    328     }
    329 
    330     /** @return true if a progress indication is currently active. */
    331     public boolean isProgressIndicationActive() {
    332         return (progressIndication != ProgressIndicationType.NONE);
    333     }
    334 
    335 
    336     //
    337     // (4) Optional overlay when a 3rd party "provider" is used.
    338     //     @see InCallScreen.updateProviderOverlay()
    339     //
    340 
    341     // TODO: maybe isolate all the provider-overlay-related stuff out to a
    342     //       separate inner class?
    343     boolean providerOverlayVisible;
    344     CharSequence providerLabel;
    345     Drawable providerIcon;
    346     Uri providerGatewayUri;
    347     // The formatted address extracted from mProviderGatewayUri. User visible.
    348     String providerAddress;
    349 
    350     /**
    351      * Set the fields related to the provider support
    352      * based on the specified intent.
    353      */
    354     public void setProviderOverlayInfo(Intent intent) {
    355         providerLabel = PhoneUtils.getProviderLabel(mContext, intent);
    356         providerIcon = PhoneUtils.getProviderIcon(mContext, intent);
    357         providerGatewayUri = PhoneUtils.getProviderGatewayUri(intent);
    358         providerAddress = PhoneUtils.formatProviderUri(providerGatewayUri);
    359         providerOverlayVisible = true;
    360 
    361         // ...but if any of the "required" fields are missing, completely
    362         // disable the overlay.
    363         if (TextUtils.isEmpty(providerLabel) || providerIcon == null ||
    364             providerGatewayUri == null || TextUtils.isEmpty(providerAddress)) {
    365             clearProviderOverlayInfo();
    366         }
    367     }
    368 
    369     /**
    370      * Clear all the fields related to the provider support.
    371      */
    372     public void clearProviderOverlayInfo() {
    373         providerOverlayVisible = false;
    374         providerLabel = null;
    375         providerIcon = null;
    376         providerGatewayUri = null;
    377         providerAddress = null;
    378     }
    379 
    380     /**
    381      * "Call origin" of the most recent phone call.
    382      *
    383      * Watch out: right now this is only used to determine where the user should go after the phone
    384      * call. See also {@link InCallScreen} for more detail. There is *no* specific specification
    385      * about how this variable will be used.
    386      *
    387      * @see PhoneApp#setLatestActiveCallOrigin(String)
    388      * @see PhoneApp#createPhoneEndIntentUsingCallOrigin()
    389      *
    390      * TODO: we should determine some public behavior for this variable.
    391      */
    392     String latestActiveCallOrigin;
    393 
    394     //
    395     // Debugging
    396     //
    397 
    398     public void dumpState() {
    399         log("dumpState():");
    400         log("  - showDialpad: " + showDialpad);
    401         if (hasPendingCallStatusCode()) {
    402             log("  - status indication is pending!");
    403             log("    - pending call status code = " + mPendingCallStatusCode);
    404         } else {
    405             log("  - pending call status code: none");
    406         }
    407         log("  - progressIndication: " + progressIndication);
    408         if (providerOverlayVisible) {
    409             log("  - provider overlay VISIBLE: "
    410                   + providerLabel + " / "
    411                   + providerIcon  + " / "
    412                   + providerGatewayUri + " / "
    413                   + providerAddress);
    414         } else {
    415             log("  - provider overlay: none");
    416         }
    417         log("  - latestActiveCallOrigin: " + latestActiveCallOrigin);
    418     }
    419 
    420     private static void log(String msg) {
    421         Log.d(TAG, msg);
    422     }
    423 }
    424