• 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 
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      */
init(Context context)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      */
InCallUiState(Context context)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      */
hasPendingCallStatusCode()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      */
getPendingCallStatusCode()216     public CallStatusCode getPendingCallStatusCode() {
217         return mPendingCallStatusCode;
218     }
219 
220     /**
221      * Sets the pending "error status indication" code.
222      */
setPendingCallStatusCode(CallStatusCode status)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      */
clearPendingCallStatusCode()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. */
setProgressIndication(ProgressIndicationType value)307     public void setProgressIndication(ProgressIndicationType value) {
308         progressIndication = value;
309     }
310 
311     /** Clears the current progressIndication. */
clearProgressIndication()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      */
getProgressIndication()320     public ProgressIndicationType getProgressIndication() {
321         return progressIndication;
322     }
323 
324     /** @return true if a progress indication is currently active. */
isProgressIndicationActive()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      */
setProviderOverlayInfo(Intent intent)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      */
clearProviderOverlayInfo()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 
dumpState()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