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