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