• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
init(Context context)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      */
InCallUiState(Context context)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      */
hasPendingCallStatusCode()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      */
getPendingCallStatusCode()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      */
setPendingCallStatusCode(CallStatusCode status)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      */
clearPendingCallStatusCode()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. */
setProgressIndication(ProgressIndicationType value)321     public void setProgressIndication(ProgressIndicationType value) {
322         progressIndication = value;
323     }
324 
325     /** Clears the current progressIndication. */
clearProgressIndication()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      */
getProgressIndication()334     public ProgressIndicationType getProgressIndication() {
335         return progressIndication;
336     }
337 
338     /** @return true if a progress indication is currently active. */
isProgressIndicationActive()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      */
setProviderInfo(Intent intent)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      */
clearProviderInfo()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 
dumpState()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 
log(String msg)450     private static void log(String msg) {
451         Log.d(TAG, msg);
452     }
453 }
454