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      * The contact/dialed number information shown in the DTMF digits text
    150      * when the user has not yet typed any digits.
    151      *
    152      * Currently only used for displaying "Voice Mail" since voicemail calls
    153      * start directly in the dialpad view.
    154      */
    155     String dialpadContextText;
    156 
    157     //
    158     // (3) Error / diagnostic indications
    159     //
    160 
    161     // This section provides an abstract concept of an "error status
    162     // indication" for some kind of exceptional condition that needs to be
    163     // communicated to the user, in the context of the in-call UI.
    164     //
    165     // If mPendingCallStatusCode is any value other than SUCCESS, that
    166     // indicates that the in-call UI needs to display a dialog to the user
    167     // with the specified title and message text.
    168     //
    169     // When an error occurs outside of the InCallScreen itself (like
    170     // during CallController.placeCall() for example), we inform the user
    171     // by doing the following steps:
    172     //
    173     // (1) set the "pending call status code" to a value other than SUCCESS
    174     //     (based on the specific error that happened)
    175     // (2) force the InCallScreen to be launched (or relaunched)
    176     // (3) InCallScreen.onResume() will notice that pending call status code
    177     //     is set, and will actually bring up the desired dialog.
    178     //
    179     // Watch out: any time you set (or change!) the pending call status code
    180     // field you must be sure to always (re)launch the InCallScreen.
    181     //
    182     // Finally, the InCallScreen itself is responsible for resetting the
    183     // pending call status code, when the user dismisses the dialog (like by
    184     // hitting the OK button or pressing Back).  The pending call status code
    185     // field is NOT cleared simply by the InCallScreen being paused or
    186     // finished, since the resulting dialog needs to persist across
    187     // orientation changes or if the screen turns off.
    188 
    189     // TODO: other features we might eventually need here:
    190     //
    191     //   - Some error status messages stay in force till reset,
    192     //     others may automatically clear themselves after
    193     //     a fixed delay
    194     //
    195     //   - Some error statuses may be visible as a dialog with an OK
    196     //     button (like "call failed"), others may be an indefinite
    197     //     progress dialog (like "turning on radio for emergency call").
    198     //
    199     //   - Eventually some error statuses may have extra actions (like a
    200     //     "retry call" button that we might provide at the bottom of the
    201     //     "call failed because you have no signal" dialog.)
    202 
    203     /**
    204      * The current pending "error status indication" that we need to
    205      * display to the user.
    206      *
    207      * If this field is set to a value other than SUCCESS, this indicates to
    208      * the InCallScreen that we need to show some kind of message to the user
    209      * (usually an error dialog) based on the specified status code.
    210      */
    211     private CallStatusCode mPendingCallStatusCode = CallStatusCode.SUCCESS;
    212 
    213     /**
    214      * @return true if there's a pending "error status indication"
    215      * that we need to display to the user.
    216      */
    217     public boolean hasPendingCallStatusCode() {
    218         if (DBG) log("hasPendingCallStatusCode() ==> "
    219                      + (mPendingCallStatusCode != CallStatusCode.SUCCESS));
    220         return (mPendingCallStatusCode != CallStatusCode.SUCCESS);
    221     }
    222 
    223     /**
    224      * @return the pending "error status indication" code
    225      * that we need to display to the user.
    226      */
    227     public CallStatusCode getPendingCallStatusCode() {
    228         if (DBG) log("getPendingCallStatusCode() ==> " + mPendingCallStatusCode);
    229         return mPendingCallStatusCode;
    230     }
    231 
    232     /**
    233      * Sets the pending "error status indication" code.
    234      */
    235     public void setPendingCallStatusCode(CallStatusCode status) {
    236         if (DBG) log("setPendingCallStatusCode( " + status + " )...");
    237         if (mPendingCallStatusCode != CallStatusCode.SUCCESS) {
    238             // Uh oh: mPendingCallStatusCode is already set to some value
    239             // other than SUCCESS (which indicates that there was some kind of
    240             // failure), and now we're trying to indicate another (potentially
    241             // different) failure.  But we can only indicate one failure at a
    242             // time to the user, so the previous pending code is now going to
    243             // be lost.
    244             Log.w(TAG, "setPendingCallStatusCode: setting new code " + status
    245                   + ", but a previous code " + mPendingCallStatusCode
    246                   + " was already pending!");
    247         }
    248         mPendingCallStatusCode = status;
    249     }
    250 
    251     /**
    252      * Clears out the pending "error status indication" code.
    253      *
    254      * This indicates that there's no longer any error or "exceptional
    255      * condition" that needs to be displayed to the user.  (Typically, this
    256      * method is called when the user dismisses the error dialog that came up
    257      * because of a previous call status code.)
    258      */
    259     public void clearPendingCallStatusCode() {
    260         if (DBG) log("clearPendingCallStatusCode()...");
    261         mPendingCallStatusCode = CallStatusCode.SUCCESS;
    262     }
    263 
    264     /**
    265      * Flag used to control the CDMA-specific "call lost" dialog.
    266      *
    267      * If true, that means that if the *next* outgoing call fails with an
    268      * abnormal disconnection cause, we need to display the "call lost"
    269      * dialog.  (Normally, in CDMA we handle some types of call failures
    270      * by automatically retrying the call.  This flag is set to true when
    271      * we're about to auto-retry, which means that if the *retry* also
    272      * fails we'll give up and display an error.)
    273      * See the logic in InCallScreen.onDisconnect() for the full story.
    274      *
    275      * TODO: the state machine that maintains the needToShowCallLostDialog
    276      * flag in InCallScreen.onDisconnect() should really be moved into the
    277      * CallController.  Then we can get rid of this extra flag, and
    278      * instead simply use the CallStatusCode value CDMA_CALL_LOST to
    279      * trigger the "call lost" dialog.
    280      */
    281     boolean needToShowCallLostDialog;
    282 
    283 
    284     //
    285     // Progress indications
    286     //
    287 
    288     /**
    289      * Possible messages we might need to display along with
    290      * an indefinite progress spinner.
    291      */
    292     public enum ProgressIndicationType {
    293         /**
    294          * No progress indication needs to be shown.
    295          */
    296         NONE,
    297 
    298         /**
    299          * Shown when making an emergency call from airplane mode;
    300          * see CallController$EmergencyCallHelper.
    301          */
    302         TURNING_ON_RADIO,
    303 
    304         /**
    305          * Generic "retrying" state.  (Specifically, this is shown while
    306          * retrying after an initial failure from the "emergency call from
    307          * airplane mode" sequence.)
    308          */
    309          RETRYING
    310     }
    311 
    312     /**
    313      * The current progress indication that should be shown
    314      * to the user.  Any value other than NONE will cause the InCallScreen
    315      * to bring up an indefinite progress spinner along with a message
    316      * corresponding to the specified ProgressIndicationType.
    317      */
    318     private ProgressIndicationType progressIndication = ProgressIndicationType.NONE;
    319 
    320     /** Sets the current progressIndication. */
    321     public void setProgressIndication(ProgressIndicationType value) {
    322         progressIndication = value;
    323     }
    324 
    325     /** Clears the current progressIndication. */
    326     public void clearProgressIndication() {
    327         progressIndication = ProgressIndicationType.NONE;
    328     }
    329 
    330     /**
    331      * @return the current progress indication type, or ProgressIndicationType.NONE
    332      * if no progress indication is currently active.
    333      */
    334     public ProgressIndicationType getProgressIndication() {
    335         return progressIndication;
    336     }
    337 
    338     /** @return true if a progress indication is currently active. */
    339     public boolean isProgressIndicationActive() {
    340         return (progressIndication != ProgressIndicationType.NONE);
    341     }
    342 
    343 
    344     //
    345     // (4) Optional info when a 3rd party "provider" is used.
    346     //     @see InCallScreen#requestRemoveProviderInfoWithDelay()
    347     //     @see CallCard#updateCallStateWidgets()
    348     //
    349 
    350     // TODO: maybe isolate all the provider-related stuff out to a
    351     //       separate inner class?
    352     boolean providerInfoVisible;
    353     CharSequence providerLabel;
    354     Drawable providerIcon;
    355     Uri providerGatewayUri;
    356     // The formatted address extracted from mProviderGatewayUri. User visible.
    357     String providerAddress;
    358 
    359     /**
    360      * Set the fields related to the provider support
    361      * based on the specified intent.
    362      */
    363     public void setProviderInfo(Intent intent) {
    364         providerLabel = PhoneUtils.getProviderLabel(mContext, intent);
    365         providerIcon = PhoneUtils.getProviderIcon(mContext, intent);
    366         providerGatewayUri = PhoneUtils.getProviderGatewayUri(intent);
    367         providerAddress = PhoneUtils.formatProviderUri(providerGatewayUri);
    368         providerInfoVisible = true;
    369 
    370         // ...but if any of the "required" fields are missing, completely
    371         // disable the overlay.
    372         if (TextUtils.isEmpty(providerLabel) || providerIcon == null ||
    373             providerGatewayUri == null || TextUtils.isEmpty(providerAddress)) {
    374             clearProviderInfo();
    375         }
    376     }
    377 
    378     /**
    379      * Clear all the fields related to the provider support.
    380      */
    381     public void clearProviderInfo() {
    382         providerInfoVisible = false;
    383         providerLabel = null;
    384         providerIcon = null;
    385         providerGatewayUri = null;
    386         providerAddress = null;
    387     }
    388 
    389     /**
    390      * "Call origin" of the most recent phone call.
    391      *
    392      * Watch out: right now this is only used to determine where the user should go after the phone
    393      * call. See also {@link InCallScreen} for more detail. There is *no* specific specification
    394      * about how this variable will be used.
    395      *
    396      * @see PhoneGlobals#setLatestActiveCallOrigin(String)
    397      * @see PhoneGlobals#createPhoneEndIntentUsingCallOrigin()
    398      *
    399      * TODO: we should determine some public behavior for this variable.
    400      */
    401     String latestActiveCallOrigin;
    402 
    403     /**
    404      * Timestamp for "Call origin". This will be used to preserve when the call origin was set.
    405      * {@link android.os.SystemClock#elapsedRealtime()} will be used.
    406      */
    407     long latestActiveCallOriginTimeStamp;
    408 
    409     /**
    410      * Flag forcing Phone app to show in-call UI even when there's no phone call and thus Phone
    411      * is in IDLE state. This will be turned on only when:
    412      *
    413      * - the last phone call is hung up, and
    414      * - the screen is being turned off in the middle of in-call UI (and thus when the screen being
    415      *   turned on in-call UI is expected to be the next foreground activity)
    416      *
    417      * At that moment whole UI should show "previously disconnected phone call" for a moment and
    418      * exit itself. {@link InCallScreen#onPause()} will turn this off and prevent possible weird
    419      * cases which may happen with that exceptional case.
    420      */
    421     boolean showAlreadyDisconnectedState;
    422 
    423     //
    424     // Debugging
    425     //
    426 
    427     public void dumpState() {
    428         log("dumpState():");
    429         log("  - showDialpad: " + showDialpad);
    430         log("    - dialpadContextText: " + dialpadContextText);
    431         if (hasPendingCallStatusCode()) {
    432             log("  - status indication is pending!");
    433             log("    - pending call status code = " + mPendingCallStatusCode);
    434         } else {
    435             log("  - pending call status code: none");
    436         }
    437         log("  - progressIndication: " + progressIndication);
    438         if (providerInfoVisible) {
    439             log("  - provider info VISIBLE: "
    440                   + providerLabel + " / "
    441                   + providerIcon  + " / "
    442                   + providerGatewayUri + " / "
    443                   + providerAddress);
    444         } else {
    445             log("  - provider info: none");
    446         }
    447         log("  - latestActiveCallOrigin: " + latestActiveCallOrigin);
    448     }
    449 
    450     private static void log(String msg) {
    451         Log.d(TAG, msg);
    452     }
    453 }
    454