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 PhoneApp#setLatestActiveCallOrigin(String) 397 * @see PhoneApp#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