blob: 3b700d7a213ffd970cc22267aa30bee4babcf41f [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
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
17package com.android.phone;
18
19import com.android.phone.Constants.CallStatusCode;
20
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.drawable.Drawable;
24import android.net.Uri;
25import android.text.TextUtils;
26import 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 */
54public 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}