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 */ init(Context context)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 */ InCallUiState(Context context)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 // 150 // (3) Error / diagnostic indications 151 // 152 153 // This section provides an abstract concept of an "error status 154 // indication" for some kind of exceptional condition that needs to be 155 // communicated to the user, in the context of the in-call UI. 156 // 157 // If mPendingCallStatusCode is any value other than SUCCESS, that 158 // indicates that the in-call UI needs to display a dialog to the user 159 // with the specified title and message text. 160 // 161 // When an error occurs outside of the InCallScreen itself (like 162 // during CallController.placeCall() for example), we inform the user 163 // by doing the following steps: 164 // 165 // (1) set the "pending call status code" to a value other than SUCCESS 166 // (based on the specific error that happened) 167 // (2) force the InCallScreen to be launched (or relaunched) 168 // (3) InCallScreen.onResume() will notice that pending call status code 169 // is set, and will actually bring up the desired dialog. 170 // 171 // Watch out: any time you set (or change!) the pending call status code 172 // field you must be sure to always (re)launch the InCallScreen. 173 // 174 // Finally, the InCallScreen itself is responsible for resetting the 175 // pending call status code, when the user dismisses the dialog (like by 176 // hitting the OK button or pressing Back). The pending call status code 177 // field is NOT cleared simply by the InCallScreen being paused or 178 // finished, since the resulting dialog needs to persist across 179 // orientation changes or if the screen turns off. 180 181 // TODO: other features we might eventually need here: 182 // 183 // - Some error status messages stay in force till reset, 184 // others may automatically clear themselves after 185 // a fixed delay 186 // 187 // - Some error statuses may be visible as a dialog with an OK 188 // button (like "call failed"), others may be an indefinite 189 // progress dialog (like "turning on radio for emergency call"). 190 // 191 // - Eventually some error statuses may have extra actions (like a 192 // "retry call" button that we might provide at the bottom of the 193 // "call failed because you have no signal" dialog.) 194 195 /** 196 * The current pending "error status indication" that we need to 197 * display to the user. 198 * 199 * If this field is set to a value other than SUCCESS, this indicates to 200 * the InCallScreen that we need to show some kind of message to the user 201 * (usually an error dialog) based on the specified status code. 202 */ 203 private CallStatusCode mPendingCallStatusCode = CallStatusCode.SUCCESS; 204 205 /** 206 * @return true if there's a pending "error status indication" 207 * that we need to display to the user. 208 */ hasPendingCallStatusCode()209 public boolean hasPendingCallStatusCode() { 210 if (DBG) log("hasPendingCallStatusCode() ==> " 211 + (mPendingCallStatusCode != CallStatusCode.SUCCESS)); 212 return (mPendingCallStatusCode != CallStatusCode.SUCCESS); 213 } 214 215 /** 216 * @return the pending "error status indication" code 217 * that we need to display to the user. 218 */ getPendingCallStatusCode()219 public CallStatusCode getPendingCallStatusCode() { 220 if (DBG) log("getPendingCallStatusCode() ==> " + mPendingCallStatusCode); 221 return mPendingCallStatusCode; 222 } 223 224 /** 225 * Sets the pending "error status indication" code. 226 */ setPendingCallStatusCode(CallStatusCode status)227 public void setPendingCallStatusCode(CallStatusCode status) { 228 if (DBG) log("setPendingCallStatusCode( " + status + " )..."); 229 if (mPendingCallStatusCode != CallStatusCode.SUCCESS) { 230 // Uh oh: mPendingCallStatusCode is already set to some value 231 // other than SUCCESS (which indicates that there was some kind of 232 // failure), and now we're trying to indicate another (potentially 233 // different) failure. But we can only indicate one failure at a 234 // time to the user, so the previous pending code is now going to 235 // be lost. 236 Log.w(TAG, "setPendingCallStatusCode: setting new code " + status 237 + ", but a previous code " + mPendingCallStatusCode 238 + " was already pending!"); 239 } 240 mPendingCallStatusCode = status; 241 } 242 243 /** 244 * Clears out the pending "error status indication" code. 245 * 246 * This indicates that there's no longer any error or "exceptional 247 * condition" that needs to be displayed to the user. (Typically, this 248 * method is called when the user dismisses the error dialog that came up 249 * because of a previous call status code.) 250 */ clearPendingCallStatusCode()251 public void clearPendingCallStatusCode() { 252 if (DBG) log("clearPendingCallStatusCode()..."); 253 mPendingCallStatusCode = CallStatusCode.SUCCESS; 254 } 255 256 /** 257 * Flag used to control the CDMA-specific "call lost" dialog. 258 * 259 * If true, that means that if the *next* outgoing call fails with an 260 * abnormal disconnection cause, we need to display the "call lost" 261 * dialog. (Normally, in CDMA we handle some types of call failures 262 * by automatically retrying the call. This flag is set to true when 263 * we're about to auto-retry, which means that if the *retry* also 264 * fails we'll give up and display an error.) 265 * See the logic in InCallScreen.onDisconnect() for the full story. 266 * 267 * TODO: the state machine that maintains the needToShowCallLostDialog 268 * flag in InCallScreen.onDisconnect() should really be moved into the 269 * CallController. Then we can get rid of this extra flag, and 270 * instead simply use the CallStatusCode value CDMA_CALL_LOST to 271 * trigger the "call lost" dialog. 272 */ 273 boolean needToShowCallLostDialog; 274 275 276 // 277 // Progress indications 278 // 279 280 /** 281 * Possible messages we might need to display along with 282 * an indefinite progress spinner. 283 */ 284 public enum ProgressIndicationType { 285 /** 286 * No progress indication needs to be shown. 287 */ 288 NONE, 289 290 /** 291 * Shown when making an emergency call from airplane mode; 292 * see CallController$EmergencyCallHelper. 293 */ 294 TURNING_ON_RADIO, 295 296 /** 297 * Generic "retrying" state. (Specifically, this is shown while 298 * retrying after an initial failure from the "emergency call from 299 * airplane mode" sequence.) 300 */ 301 RETRYING 302 } 303 304 /** 305 * The current progress indication that should be shown 306 * to the user. Any value other than NONE will cause the InCallScreen 307 * to bring up an indefinite progress spinner along with a message 308 * corresponding to the specified ProgressIndicationType. 309 */ 310 private ProgressIndicationType progressIndication = ProgressIndicationType.NONE; 311 312 /** Sets the current progressIndication. */ setProgressIndication(ProgressIndicationType value)313 public void setProgressIndication(ProgressIndicationType value) { 314 progressIndication = value; 315 } 316 317 /** Clears the current progressIndication. */ clearProgressIndication()318 public void clearProgressIndication() { 319 progressIndication = ProgressIndicationType.NONE; 320 } 321 322 /** 323 * @return the current progress indication type, or ProgressIndicationType.NONE 324 * if no progress indication is currently active. 325 */ getProgressIndication()326 public ProgressIndicationType getProgressIndication() { 327 return progressIndication; 328 } 329 330 /** @return true if a progress indication is currently active. */ isProgressIndicationActive()331 public boolean isProgressIndicationActive() { 332 return (progressIndication != ProgressIndicationType.NONE); 333 } 334 335 336 // 337 // (4) Optional info when a 3rd party "provider" is used. 338 // @see InCallScreen#requestRemoveProviderInfoWithDelay() 339 // @see CallCard#updateCallStateWidgets() 340 // 341 342 // TODO: maybe isolate all the provider-related stuff out to a 343 // separate inner class? 344 boolean providerInfoVisible; 345 CharSequence providerLabel; 346 Drawable providerIcon; 347 Uri providerGatewayUri; 348 // The formatted address extracted from mProviderGatewayUri. User visible. 349 String providerAddress; 350 351 /** 352 * Set the fields related to the provider support 353 * based on the specified intent. 354 */ setProviderInfo(Intent intent)355 public void setProviderInfo(Intent intent) { 356 providerLabel = PhoneUtils.getProviderLabel(mContext, intent); 357 providerIcon = PhoneUtils.getProviderIcon(mContext, intent); 358 providerGatewayUri = PhoneUtils.getProviderGatewayUri(intent); 359 providerAddress = PhoneUtils.formatProviderUri(providerGatewayUri); 360 providerInfoVisible = true; 361 362 // ...but if any of the "required" fields are missing, completely 363 // disable the overlay. 364 if (TextUtils.isEmpty(providerLabel) || providerIcon == null || 365 providerGatewayUri == null || TextUtils.isEmpty(providerAddress)) { 366 clearProviderInfo(); 367 } 368 } 369 370 /** 371 * Clear all the fields related to the provider support. 372 */ clearProviderInfo()373 public void clearProviderInfo() { 374 providerInfoVisible = false; 375 providerLabel = null; 376 providerIcon = null; 377 providerGatewayUri = null; 378 providerAddress = null; 379 } 380 381 /** 382 * "Call origin" of the most recent phone call. 383 * 384 * Watch out: right now this is only used to determine where the user should go after the phone 385 * call. See also {@link InCallScreen} for more detail. There is *no* specific specification 386 * about how this variable will be used. 387 * 388 * @see PhoneApp#setLatestActiveCallOrigin(String) 389 * @see PhoneApp#createPhoneEndIntentUsingCallOrigin() 390 * 391 * TODO: we should determine some public behavior for this variable. 392 */ 393 String latestActiveCallOrigin; 394 395 /** 396 * Timestamp for "Call origin". This will be used to preserve when the call origin was set. 397 * {@link android.os.SystemClock#elapsedRealtime()} will be used. 398 */ 399 long latestActiveCallOriginTimeStamp; 400 401 /** 402 * Flag forcing Phone app to show in-call UI even when there's no phone call and thus Phone 403 * is in IDLE state. This will be turned on only when: 404 * 405 * - the last phone call is hung up, and 406 * - the screen is being turned off in the middle of in-call UI (and thus when the screen being 407 * turned on in-call UI is expected to be the next foreground activity) 408 * 409 * At that moment whole UI should show "previously disconnected phone call" for a moment and 410 * exit itself. {@link InCallScreen#onPause()} will turn this off and prevent possible weird 411 * cases which may happen with that exceptional case. 412 */ 413 boolean showAlreadyDisconnectedState; 414 415 // 416 // Debugging 417 // 418 dumpState()419 public void dumpState() { 420 log("dumpState():"); 421 log(" - showDialpad: " + showDialpad); 422 if (hasPendingCallStatusCode()) { 423 log(" - status indication is pending!"); 424 log(" - pending call status code = " + mPendingCallStatusCode); 425 } else { 426 log(" - pending call status code: none"); 427 } 428 log(" - progressIndication: " + progressIndication); 429 if (providerInfoVisible) { 430 log(" - provider info VISIBLE: " 431 + providerLabel + " / " 432 + providerIcon + " / " 433 + providerGatewayUri + " / " 434 + providerAddress); 435 } else { 436 log(" - provider info: none"); 437 } 438 log(" - latestActiveCallOrigin: " + latestActiveCallOrigin); 439 } 440 log(String msg)441 private static void log(String msg) { 442 Log.d(TAG, msg); 443 } 444 } 445