summaryrefslogtreecommitdiff
path: root/src/com/android/phone/InCallUiState.java
blob: 3b700d7a213ffd970cc22267aa30bee4babcf41f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.phone;

import com.android.phone.Constants.CallStatusCode;

import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;


/**
 * Helper class to keep track of "persistent state" of the in-call UI.
 *
 * The onscreen appearance of the in-call UI mostly depends on the current
 * Call/Connection state, which is owned by the telephony framework.  But
 * there's some application-level "UI state" too, which lives here in the
 * phone app.
 *
 * This application-level state information is *not* maintained by the
 * InCallScreen, since it needs to persist throughout an entire phone call,
 * not just a single resume/pause cycle of the InCallScreen.  So instead, that
 * state is stored here, in a singleton instance of this class.
 *
 * The state kept here is a high-level abstraction of in-call UI state: we
 * don't know about implementation details like specific widgets or strings or
 * resources, but we do understand higher level concepts (for example "is the
 * dialpad visible") and high-level modes (like InCallScreenMode) and error
 * conditions (like CallStatusCode).
 *
 * @see InCallControlState for a separate collection of "UI state" that
 * controls all the onscreen buttons of the in-call UI, based on the state of
 * the telephony layer.
 *
 * The singleton instance of this class is owned by the PhoneApp instance.
 */
public class InCallUiState {
    private static final String TAG = "InCallUiState";
    private static final boolean DBG = false;

    /** The singleton InCallUiState instance. */
    private static InCallUiState sInstance;

    private Context mContext;

    /**
     * Initialize the singleton InCallUiState instance.
     *
     * This is only done once, at startup, from PhoneApp.onCreate().
     * From then on, the InCallUiState instance is available via the
     * PhoneApp's public "inCallUiState" field, which is why there's no
     * getInstance() method here.
     */
    /* package */ static InCallUiState init(Context context) {
        synchronized (InCallUiState.class) {
            if (sInstance == null) {
                sInstance = new InCallUiState(context);
            } else {
                Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
            }
            return sInstance;
        }
    }

    /**
     * Private constructor (this is a singleton).
     * @see init()
     */
    private InCallUiState(Context context) {
        mContext = context;
    }


    //
    // (1) High-level state of the whole in-call UI
    //

    /** High-level "modes" of the in-call UI. */
    public enum InCallScreenMode {
        /**
         * Normal in-call UI elements visible.
         */
        NORMAL,
        /**
         * "Manage conference" UI is visible, totally replacing the
         * normal in-call UI.
         */
        MANAGE_CONFERENCE,
        /**
         * Non-interactive UI state.  Call card is visible,
         * displaying information about the call that just ended.
         */
        CALL_ENDED,
        /**
         * Normal OTA in-call UI elements visible.
         */
        OTA_NORMAL,
        /**
         * OTA call ended UI visible, replacing normal OTA in-call UI.
         */
        OTA_ENDED,
        /**
         * Default state when not on call
         */
        UNDEFINED
    }

    /** Current high-level "mode" of the in-call UI. */
    InCallScreenMode inCallScreenMode = InCallScreenMode.UNDEFINED;


    //
    // (2) State of specific UI elements
    //

    /**
     * Is the onscreen twelve-key dialpad visible?
     */
    boolean showDialpad;

    /**
     * The contents of the twelve-key dialpad's "digits" display, which is
     * visible only when the dialpad itself is visible.
     *
     * (This is basically the "history" of DTMF digits you've typed so far
     * in the current call.  It's cleared out any time a new call starts,
     * to make sure the digits don't persist between two separate calls.)
     */
    String dialpadDigits;

    /**
     * The contact/dialed number information shown in the DTMF digits text
     * when the user has not yet typed any digits.
     *
     * Currently only used for displaying "Voice Mail" since voicemail calls
     * start directly in the dialpad view.
     */
    String dialpadContextText;

    //
    // (3) Error / diagnostic indications
    //

    // This section provides an abstract concept of an "error status
    // indication" for some kind of exceptional condition that needs to be
    // communicated to the user, in the context of the in-call UI.
    //
    // If mPendingCallStatusCode is any value other than SUCCESS, that
    // indicates that the in-call UI needs to display a dialog to the user
    // with the specified title and message text.
    //
    // When an error occurs outside of the InCallScreen itself (like
    // during CallController.placeCall() for example), we inform the user
    // by doing the following steps:
    //
    // (1) set the "pending call status code" to a value other than SUCCESS
    //     (based on the specific error that happened)
    // (2) force the InCallScreen to be launched (or relaunched)
    // (3) InCallScreen.onResume() will notice that pending call status code
    //     is set, and will actually bring up the desired dialog.
    //
    // Watch out: any time you set (or change!) the pending call status code
    // field you must be sure to always (re)launch the InCallScreen.
    //
    // Finally, the InCallScreen itself is responsible for resetting the
    // pending call status code, when the user dismisses the dialog (like by
    // hitting the OK button or pressing Back).  The pending call status code
    // field is NOT cleared simply by the InCallScreen being paused or
    // finished, since the resulting dialog needs to persist across
    // orientation changes or if the screen turns off.

    // TODO: other features we might eventually need here:
    //
    //   - Some error status messages stay in force till reset,
    //     others may automatically clear themselves after
    //     a fixed delay
    //
    //   - Some error statuses may be visible as a dialog with an OK
    //     button (like "call failed"), others may be an indefinite
    //     progress dialog (like "turning on radio for emergency call").
    //
    //   - Eventually some error statuses may have extra actions (like a
    //     "retry call" button that we might provide at the bottom of the
    //     "call failed because you have no signal" dialog.)

    /**
     * The current pending "error status indication" that we need to
     * display to the user.
     *
     * If this field is set to a value other than SUCCESS, this indicates to
     * the InCallScreen that we need to show some kind of message to the user
     * (usually an error dialog) based on the specified status code.
     */
    private CallStatusCode mPendingCallStatusCode = CallStatusCode.SUCCESS;

    /**
     * @return true if there's a pending "error status indication"
     * that we need to display to the user.
     */
    public boolean hasPendingCallStatusCode() {
        if (DBG) log("hasPendingCallStatusCode() ==> "
                     + (mPendingCallStatusCode != CallStatusCode.SUCCESS));
        return (mPendingCallStatusCode != CallStatusCode.SUCCESS);
    }

    /**
     * @return the pending "error status indication" code
     * that we need to display to the user.
     */
    public CallStatusCode getPendingCallStatusCode() {
        if (DBG) log("getPendingCallStatusCode() ==> " + mPendingCallStatusCode);
        return mPendingCallStatusCode;
    }

    /**
     * Sets the pending "error status indication" code.
     */
    public void setPendingCallStatusCode(CallStatusCode status) {
        if (DBG) log("setPendingCallStatusCode( " + status + " )...");
        if (mPendingCallStatusCode != CallStatusCode.SUCCESS) {
            // Uh oh: mPendingCallStatusCode is already set to some value
            // other than SUCCESS (which indicates that there was some kind of
            // failure), and now we're trying to indicate another (potentially
            // different) failure.  But we can only indicate one failure at a
            // time to the user, so the previous pending code is now going to
            // be lost.
            Log.w(TAG, "setPendingCallStatusCode: setting new code " + status
                  + ", but a previous code " + mPendingCallStatusCode
                  + " was already pending!");
        }
        mPendingCallStatusCode = status;
    }

    /**
     * Clears out the pending "error status indication" code.
     *
     * This indicates that there's no longer any error or "exceptional
     * condition" that needs to be displayed to the user.  (Typically, this
     * method is called when the user dismisses the error dialog that came up
     * because of a previous call status code.)
     */
    public void clearPendingCallStatusCode() {
        if (DBG) log("clearPendingCallStatusCode()...");
        mPendingCallStatusCode = CallStatusCode.SUCCESS;
    }

    /**
     * Flag used to control the CDMA-specific "call lost" dialog.
     *
     * If true, that means that if the *next* outgoing call fails with an
     * abnormal disconnection cause, we need to display the "call lost"
     * dialog.  (Normally, in CDMA we handle some types of call failures
     * by automatically retrying the call.  This flag is set to true when
     * we're about to auto-retry, which means that if the *retry* also
     * fails we'll give up and display an error.)
     * See the logic in InCallScreen.onDisconnect() for the full story.
     *
     * TODO: the state machine that maintains the needToShowCallLostDialog
     * flag in InCallScreen.onDisconnect() should really be moved into the
     * CallController.  Then we can get rid of this extra flag, and
     * instead simply use the CallStatusCode value CDMA_CALL_LOST to
     * trigger the "call lost" dialog.
     */
    boolean needToShowCallLostDialog;


    //
    // Progress indications
    //

    /**
     * Possible messages we might need to display along with
     * an indefinite progress spinner.
     */
    public enum ProgressIndicationType {
        /**
         * No progress indication needs to be shown.
         */
        NONE,

        /**
         * Shown when making an emergency call from airplane mode;
         * see CallController$EmergencyCallHelper.
         */
        TURNING_ON_RADIO,

        /**
         * Generic "retrying" state.  (Specifically, this is shown while
         * retrying after an initial failure from the "emergency call from
         * airplane mode" sequence.)
         */
         RETRYING
    }

    /**
     * The current progress indication that should be shown
     * to the user.  Any value other than NONE will cause the InCallScreen
     * to bring up an indefinite progress spinner along with a message
     * corresponding to the specified ProgressIndicationType.
     */
    private ProgressIndicationType progressIndication = ProgressIndicationType.NONE;

    /** Sets the current progressIndication. */
    public void setProgressIndication(ProgressIndicationType value) {
        progressIndication = value;
    }

    /** Clears the current progressIndication. */
    public void clearProgressIndication() {
        progressIndication = ProgressIndicationType.NONE;
    }

    /**
     * @return the current progress indication type, or ProgressIndicationType.NONE
     * if no progress indication is currently active.
     */
    public ProgressIndicationType getProgressIndication() {
        return progressIndication;
    }

    /** @return true if a progress indication is currently active. */
    public boolean isProgressIndicationActive() {
        return (progressIndication != ProgressIndicationType.NONE);
    }


    //
    // (4) Optional info when a 3rd party "provider" is used.
    //     @see InCallScreen#requestRemoveProviderInfoWithDelay()
    //     @see CallCard#updateCallStateWidgets()
    //

    // TODO: maybe isolate all the provider-related stuff out to a
    //       separate inner class?
    boolean providerInfoVisible;
    CharSequence providerLabel;
    Drawable providerIcon;
    Uri providerGatewayUri;
    // The formatted address extracted from mProviderGatewayUri. User visible.
    String providerAddress;

    /**
     * Set the fields related to the provider support
     * based on the specified intent.
     */
    public void setProviderInfo(Intent intent) {
        providerLabel = PhoneUtils.getProviderLabel(mContext, intent);
        providerIcon = PhoneUtils.getProviderIcon(mContext, intent);
        providerGatewayUri = PhoneUtils.getProviderGatewayUri(intent);
        providerAddress = PhoneUtils.formatProviderUri(providerGatewayUri);
        providerInfoVisible = true;

        // ...but if any of the "required" fields are missing, completely
        // disable the overlay.
        if (TextUtils.isEmpty(providerLabel) || providerIcon == null ||
            providerGatewayUri == null || TextUtils.isEmpty(providerAddress)) {
            clearProviderInfo();
        }
    }

    /**
     * Clear all the fields related to the provider support.
     */
    public void clearProviderInfo() {
        providerInfoVisible = false;
        providerLabel = null;
        providerIcon = null;
        providerGatewayUri = null;
        providerAddress = null;
    }

    /**
     * "Call origin" of the most recent phone call.
     *
     * Watch out: right now this is only used to determine where the user should go after the phone
     * call. See also {@link InCallScreen} for more detail. There is *no* specific specification
     * about how this variable will be used.
     *
     * @see PhoneGlobals#setLatestActiveCallOrigin(String)
     * @see PhoneGlobals#createPhoneEndIntentUsingCallOrigin()
     *
     * TODO: we should determine some public behavior for this variable.
     */
    String latestActiveCallOrigin;

    /**
     * Timestamp for "Call origin". This will be used to preserve when the call origin was set.
     * {@link android.os.SystemClock#elapsedRealtime()} will be used.
     */
    long latestActiveCallOriginTimeStamp;

    /**
     * Flag forcing Phone app to show in-call UI even when there's no phone call and thus Phone
     * is in IDLE state. This will be turned on only when:
     *
     * - the last phone call is hung up, and
     * - the screen is being turned off in the middle of in-call UI (and thus when the screen being
     *   turned on in-call UI is expected to be the next foreground activity)
     *
     * At that moment whole UI should show "previously disconnected phone call" for a moment and
     * exit itself. {@link InCallScreen#onPause()} will turn this off and prevent possible weird
     * cases which may happen with that exceptional case.
     */
    boolean showAlreadyDisconnectedState;

    //
    // Debugging
    //

    public void dumpState() {
        log("dumpState():");
        log("  - showDialpad: " + showDialpad);
        log("    - dialpadContextText: " + dialpadContextText);
        if (hasPendingCallStatusCode()) {
            log("  - status indication is pending!");
            log("    - pending call status code = " + mPendingCallStatusCode);
        } else {
            log("  - pending call status code: none");
        }
        log("  - progressIndication: " + progressIndication);
        if (providerInfoVisible) {
            log("  - provider info VISIBLE: "
                  + providerLabel + " / "
                  + providerIcon  + " / "
                  + providerGatewayUri + " / "
                  + providerAddress);
        } else {
            log("  - provider info: none");
        }
        log("  - latestActiveCallOrigin: " + latestActiveCallOrigin);
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}