1 /* 2 * Copyright (C) 2013 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.incallui; 18 19 import com.android.contacts.common.CallUtil; 20 import com.android.contacts.common.testing.NeededForTesting; 21 import com.android.incallui.CallList.Listener; 22 23 import android.content.Context; 24 import android.hardware.camera2.CameraCharacteristics; 25 import android.net.Uri; 26 import android.os.Bundle; 27 import android.os.Trace; 28 import android.telecom.DisconnectCause; 29 import android.telecom.GatewayInfo; 30 import android.telecom.InCallService.VideoCall; 31 import android.telecom.PhoneAccountHandle; 32 import android.telecom.VideoProfile; 33 import android.text.TextUtils; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Locale; 38 39 /** 40 * Describes a single call and its state. 41 */ 42 @NeededForTesting 43 public class Call { 44 /* Defines different states of this call */ 45 public static class State { 46 public static final int INVALID = 0; 47 public static final int NEW = 1; /* The call is new. */ 48 public static final int IDLE = 2; /* The call is idle. Nothing active */ 49 public static final int ACTIVE = 3; /* There is an active call */ 50 public static final int INCOMING = 4; /* A normal incoming phone call */ 51 public static final int CALL_WAITING = 5; /* Incoming call while another is active */ 52 public static final int DIALING = 6; /* An outgoing call during dial phase */ 53 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */ 54 public static final int ONHOLD = 8; /* An active phone call placed on hold */ 55 public static final int DISCONNECTING = 9; /* A call is being ended. */ 56 public static final int DISCONNECTED = 10; /* State after a call disconnects */ 57 public static final int CONFERENCED = 11; /* Call part of a conference call */ 58 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */ 59 public static final int CONNECTING = 13; /* Waiting for Telecomm broadcast to finish */ 60 61 62 public static boolean isConnectingOrConnected(int state) { 63 switch(state) { 64 case ACTIVE: 65 case INCOMING: 66 case CALL_WAITING: 67 case CONNECTING: 68 case DIALING: 69 case REDIALING: 70 case ONHOLD: 71 case CONFERENCED: 72 return true; 73 default: 74 } 75 return false; 76 } 77 78 public static boolean isDialing(int state) { 79 return state == DIALING || state == REDIALING; 80 } 81 82 public static String toString(int state) { 83 switch (state) { 84 case INVALID: 85 return "INVALID"; 86 case NEW: 87 return "NEW"; 88 case IDLE: 89 return "IDLE"; 90 case ACTIVE: 91 return "ACTIVE"; 92 case INCOMING: 93 return "INCOMING"; 94 case CALL_WAITING: 95 return "CALL_WAITING"; 96 case DIALING: 97 return "DIALING"; 98 case REDIALING: 99 return "REDIALING"; 100 case ONHOLD: 101 return "ONHOLD"; 102 case DISCONNECTING: 103 return "DISCONNECTING"; 104 case DISCONNECTED: 105 return "DISCONNECTED"; 106 case CONFERENCED: 107 return "CONFERENCED"; 108 case SELECT_PHONE_ACCOUNT: 109 return "SELECT_PHONE_ACCOUNT"; 110 case CONNECTING: 111 return "CONNECTING"; 112 default: 113 return "UNKNOWN"; 114 } 115 } 116 } 117 118 /** 119 * Defines different states of session modify requests, which are used to upgrade to video, or 120 * downgrade to audio. 121 */ 122 public static class SessionModificationState { 123 public static final int NO_REQUEST = 0; 124 public static final int WAITING_FOR_RESPONSE = 1; 125 public static final int REQUEST_FAILED = 2; 126 public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3; 127 public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4; 128 public static final int REQUEST_REJECTED = 5; 129 } 130 131 public static class VideoSettings { 132 public static final int CAMERA_DIRECTION_UNKNOWN = -1; 133 public static final int CAMERA_DIRECTION_FRONT_FACING = 134 CameraCharacteristics.LENS_FACING_FRONT; 135 public static final int CAMERA_DIRECTION_BACK_FACING = 136 CameraCharacteristics.LENS_FACING_BACK; 137 138 private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN; 139 140 /** 141 * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, 142 * the video state of the call should be used to infer the camera direction. 143 * 144 * @see {@link CameraCharacteristics#LENS_FACING_FRONT} 145 * @see {@link CameraCharacteristics#LENS_FACING_BACK} 146 */ 147 public void setCameraDir(int cameraDirection) { 148 if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING 149 || cameraDirection == CAMERA_DIRECTION_BACK_FACING) { 150 mCameraDirection = cameraDirection; 151 } else { 152 mCameraDirection = CAMERA_DIRECTION_UNKNOWN; 153 } 154 } 155 156 /** 157 * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN, 158 * the video state of the call should be used to infer the camera direction. 159 * 160 * @see {@link CameraCharacteristics#LENS_FACING_FRONT} 161 * @see {@link CameraCharacteristics#LENS_FACING_BACK} 162 */ 163 public int getCameraDir() { 164 return mCameraDirection; 165 } 166 167 public String toString() { 168 return "(CameraDir:" + getCameraDir() + ")"; 169 } 170 } 171 172 173 private static final String ID_PREFIX = Call.class.getSimpleName() + "_"; 174 private static int sIdCounter = 0; 175 176 private android.telecom.Call.Callback mTelecomCallCallback = 177 new android.telecom.Call.Callback() { 178 @Override 179 public void onStateChanged(android.telecom.Call call, int newState) { 180 Log.d(this, "TelecommCallCallback onStateChanged call=" + call + " newState=" 181 + newState); 182 update(); 183 } 184 185 @Override 186 public void onParentChanged(android.telecom.Call call, 187 android.telecom.Call newParent) { 188 Log.d(this, "TelecommCallCallback onParentChanged call=" + call + " newParent=" 189 + newParent); 190 update(); 191 } 192 193 @Override 194 public void onChildrenChanged(android.telecom.Call call, 195 List<android.telecom.Call> children) { 196 update(); 197 } 198 199 @Override 200 public void onDetailsChanged(android.telecom.Call call, 201 android.telecom.Call.Details details) { 202 Log.d(this, "TelecommCallCallback onStateChanged call=" + call + " details=" 203 + details); 204 update(); 205 } 206 207 @Override 208 public void onCannedTextResponsesLoaded(android.telecom.Call call, 209 List<String> cannedTextResponses) { 210 Log.d(this, "TelecommCallCallback onStateChanged call=" + call 211 + " cannedTextResponses=" + cannedTextResponses); 212 update(); 213 } 214 215 @Override 216 public void onPostDialWait(android.telecom.Call call, 217 String remainingPostDialSequence) { 218 Log.d(this, "TelecommCallCallback onStateChanged call=" + call 219 + " remainingPostDialSequence=" + remainingPostDialSequence); 220 update(); 221 } 222 223 @Override 224 public void onVideoCallChanged(android.telecom.Call call, 225 VideoCall videoCall) { 226 Log.d(this, "TelecommCallCallback onStateChanged call=" + call + " videoCall=" 227 + videoCall); 228 update(); 229 } 230 231 @Override 232 public void onCallDestroyed(android.telecom.Call call) { 233 Log.d(this, "TelecommCallCallback onStateChanged call=" + call); 234 call.unregisterCallback(mTelecomCallCallback); 235 } 236 237 @Override 238 public void onConferenceableCallsChanged(android.telecom.Call call, 239 List<android.telecom.Call> conferenceableCalls) { 240 update(); 241 } 242 }; 243 244 private android.telecom.Call mTelecommCall; 245 private final String mId; 246 private int mState = State.INVALID; 247 private DisconnectCause mDisconnectCause; 248 private int mSessionModificationState; 249 private final List<String> mChildCallIds = new ArrayList<>(); 250 private final VideoSettings mVideoSettings = new VideoSettings(); 251 /** 252 * mModifyToVideoState is used to store requested upgrade / downgrade video state 253 */ 254 private int mModifyToVideoState = VideoProfile.STATE_AUDIO_ONLY; 255 256 private InCallVideoCallCallback mVideoCallCallback; 257 258 /** 259 * Used only to create mock calls for testing 260 */ 261 @NeededForTesting 262 Call(int state) { 263 mTelecommCall = null; 264 mId = ID_PREFIX + Integer.toString(sIdCounter++); 265 setState(state); 266 } 267 268 public Call(android.telecom.Call telecommCall) { 269 mTelecommCall = telecommCall; 270 mId = ID_PREFIX + Integer.toString(sIdCounter++); 271 updateFromTelecommCall(); 272 mTelecommCall.registerCallback(mTelecomCallCallback); 273 } 274 275 public android.telecom.Call getTelecommCall() { 276 return mTelecommCall; 277 } 278 279 /** 280 * @return video settings of the call, null if the call is not a video call. 281 * @see VideoProfile 282 */ 283 public VideoSettings getVideoSettings() { 284 return mVideoSettings; 285 } 286 287 private void update() { 288 Trace.beginSection("Update"); 289 int oldState = getState(); 290 updateFromTelecommCall(); 291 if (oldState != getState() && getState() == Call.State.DISCONNECTED) { 292 CallList.getInstance().onDisconnect(this); 293 } else { 294 CallList.getInstance().onUpdate(this); 295 } 296 Trace.endSection(); 297 } 298 299 private void updateFromTelecommCall() { 300 Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString()); 301 setState(translateState(mTelecommCall.getState())); 302 setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause()); 303 304 if (mTelecommCall.getVideoCall() != null) { 305 if (mVideoCallCallback == null) { 306 mVideoCallCallback = new InCallVideoCallCallback(this); 307 } 308 mTelecommCall.getVideoCall().registerCallback(mVideoCallCallback); 309 } 310 311 mChildCallIds.clear(); 312 for (int i = 0; i < mTelecommCall.getChildren().size(); i++) { 313 mChildCallIds.add( 314 CallList.getInstance().getCallByTelecommCall( 315 mTelecommCall.getChildren().get(i)).getId()); 316 } 317 } 318 319 private static int translateState(int state) { 320 switch (state) { 321 case android.telecom.Call.STATE_NEW: 322 case android.telecom.Call.STATE_CONNECTING: 323 return Call.State.CONNECTING; 324 case android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT: 325 return Call.State.SELECT_PHONE_ACCOUNT; 326 case android.telecom.Call.STATE_DIALING: 327 return Call.State.DIALING; 328 case android.telecom.Call.STATE_RINGING: 329 return Call.State.INCOMING; 330 case android.telecom.Call.STATE_ACTIVE: 331 return Call.State.ACTIVE; 332 case android.telecom.Call.STATE_HOLDING: 333 return Call.State.ONHOLD; 334 case android.telecom.Call.STATE_DISCONNECTED: 335 return Call.State.DISCONNECTED; 336 case android.telecom.Call.STATE_DISCONNECTING: 337 return Call.State.DISCONNECTING; 338 default: 339 return Call.State.INVALID; 340 } 341 } 342 343 public String getId() { 344 return mId; 345 } 346 347 public String getNumber() { 348 if (mTelecommCall == null) { 349 return null; 350 } 351 if (mTelecommCall.getDetails().getGatewayInfo() != null) { 352 return mTelecommCall.getDetails().getGatewayInfo() 353 .getOriginalAddress().getSchemeSpecificPart(); 354 } 355 return getHandle() == null ? null : getHandle().getSchemeSpecificPart(); 356 } 357 358 public Uri getHandle() { 359 return mTelecommCall == null ? null : mTelecommCall.getDetails().getHandle(); 360 } 361 362 public int getState() { 363 if (mTelecommCall != null && mTelecommCall.getParent() != null) { 364 return State.CONFERENCED; 365 } else { 366 return mState; 367 } 368 } 369 370 public void setState(int state) { 371 mState = state; 372 } 373 374 public int getNumberPresentation() { 375 return mTelecommCall == null ? null : mTelecommCall.getDetails().getHandlePresentation(); 376 } 377 378 public int getCnapNamePresentation() { 379 return mTelecommCall == null ? null 380 : mTelecommCall.getDetails().getCallerDisplayNamePresentation(); 381 } 382 383 public String getCnapName() { 384 return mTelecommCall == null ? null 385 : getTelecommCall().getDetails().getCallerDisplayName(); 386 } 387 388 public Bundle getIntentExtras() { 389 return mTelecommCall == null ? null : mTelecommCall.getDetails().getIntentExtras(); 390 } 391 392 public Bundle getExtras() { 393 return mTelecommCall == null ? null : mTelecommCall.getDetails().getExtras(); 394 } 395 396 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */ 397 public DisconnectCause getDisconnectCause() { 398 if (mState == State.DISCONNECTED || mState == State.IDLE) { 399 return mDisconnectCause; 400 } 401 402 return new DisconnectCause(DisconnectCause.UNKNOWN); 403 } 404 405 public void setDisconnectCause(DisconnectCause disconnectCause) { 406 mDisconnectCause = disconnectCause; 407 } 408 409 /** Returns the possible text message responses. */ 410 public List<String> getCannedSmsResponses() { 411 return mTelecommCall.getCannedTextResponses(); 412 } 413 414 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */ 415 public boolean can(int capabilities) { 416 int supportedCapabilities = mTelecommCall.getDetails().getCallCapabilities(); 417 418 if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) { 419 // We allow you to merge if the capabilities allow it or if it is a call with 420 // conferenceable calls. 421 if (mTelecommCall.getConferenceableCalls().isEmpty() && 422 ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE 423 & supportedCapabilities) == 0)) { 424 // Cannot merge calls if there are no calls to merge with. 425 return false; 426 } 427 capabilities &= ~android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE; 428 } 429 return (capabilities == (capabilities & mTelecommCall.getDetails().getCallCapabilities())); 430 } 431 432 public boolean hasProperty(int property) { 433 return mTelecommCall.getDetails().hasProperty(property); 434 } 435 436 /** Gets the time when the call first became active. */ 437 public long getConnectTimeMillis() { 438 return mTelecommCall.getDetails().getConnectTimeMillis(); 439 } 440 441 public boolean isConferenceCall() { 442 return mTelecommCall.getDetails().hasProperty( 443 android.telecom.Call.Details.PROPERTY_CONFERENCE); 444 } 445 446 public GatewayInfo getGatewayInfo() { 447 return mTelecommCall == null ? null : mTelecommCall.getDetails().getGatewayInfo(); 448 } 449 450 public PhoneAccountHandle getAccountHandle() { 451 return mTelecommCall == null ? null : mTelecommCall.getDetails().getAccountHandle(); 452 } 453 454 public VideoCall getVideoCall() { 455 return mTelecommCall == null ? null : mTelecommCall.getVideoCall(); 456 } 457 458 public List<String> getChildCallIds() { 459 return mChildCallIds; 460 } 461 462 public String getParentId() { 463 android.telecom.Call parentCall = mTelecommCall.getParent(); 464 if (parentCall != null) { 465 return CallList.getInstance().getCallByTelecommCall(parentCall).getId(); 466 } 467 return null; 468 } 469 470 public int getVideoState() { 471 return mTelecommCall.getDetails().getVideoState(); 472 } 473 474 public boolean isVideoCall(Context context) { 475 return CallUtil.isVideoEnabled(context) && 476 CallUtils.isVideoCall(getVideoState()); 477 } 478 479 /** 480 * This method is called when we request for a video upgrade or downgrade. This handles the 481 * session modification state RECEIVED_UPGRADE_TO_VIDEO_REQUEST and sets the video state we 482 * want to upgrade/downgrade to. 483 */ 484 public void setSessionModificationTo(int videoState) { 485 Log.d(this, "setSessionModificationTo - video state= " + videoState); 486 if (videoState == getVideoState()) { 487 mSessionModificationState = Call.SessionModificationState.NO_REQUEST; 488 Log.w(this,"setSessionModificationTo - Clearing session modification state"); 489 } else { 490 mSessionModificationState = 491 Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST; 492 setModifyToVideoState(videoState); 493 CallList.getInstance().onUpgradeToVideo(this); 494 } 495 496 Log.d(this, "setSessionModificationTo - mSessionModificationState=" 497 + mSessionModificationState + " video state= " + videoState); 498 update(); 499 } 500 501 /** 502 * This method is called to handle any other session modification states other than 503 * RECEIVED_UPGRADE_TO_VIDEO_REQUEST. We set the modification state and reset the video state 504 * when an upgrade request has been completed or failed. 505 */ 506 public void setSessionModificationState(int state) { 507 if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { 508 Log.e(this, 509 "setSessionModificationState not to be called for RECEIVED_UPGRADE_TO_VIDEO_REQUEST"); 510 return; 511 } 512 513 boolean hasChanged = mSessionModificationState != state; 514 mSessionModificationState = state; 515 Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" 516 + mSessionModificationState); 517 if (hasChanged) { 518 CallList.getInstance().onSessionModificationStateChange(this, state); 519 } 520 } 521 522 private void setModifyToVideoState(int newVideoState) { 523 mModifyToVideoState = newVideoState; 524 } 525 526 public int getModifyToVideoState() { 527 return mModifyToVideoState; 528 } 529 530 public static boolean areSame(Call call1, Call call2) { 531 if (call1 == null && call2 == null) { 532 return true; 533 } else if (call1 == null || call2 == null) { 534 return false; 535 } 536 537 // otherwise compare call Ids 538 return call1.getId().equals(call2.getId()); 539 } 540 541 public static boolean areSameNumber(Call call1, Call call2) { 542 if (call1 == null && call2 == null) { 543 return true; 544 } else if (call1 == null || call2 == null) { 545 return false; 546 } 547 548 // otherwise compare call Numbers 549 return TextUtils.equals(call1.getNumber(), call2.getNumber()); 550 } 551 552 public int getSessionModificationState() { 553 return mSessionModificationState; 554 } 555 556 @Override 557 public String toString() { 558 if (mTelecommCall == null) { 559 // This should happen only in testing since otherwise we would never have a null 560 // Telecom call. 561 return String.valueOf(mId); 562 } 563 564 return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " + 565 "videoState:%s, mSessionModificationState:%d, VideoSettings:%s]", 566 mId, 567 State.toString(getState()), 568 android.telecom.Call.Details 569 .capabilitiesToString(mTelecommCall.getDetails().getCallCapabilities()), 570 mChildCallIds, 571 getParentId(), 572 this.mTelecommCall.getConferenceableCalls(), 573 VideoProfile.videoStateToString(mTelecommCall.getDetails().getVideoState()), 574 mSessionModificationState, 575 getVideoSettings()); 576 } 577 578 public String toSimpleString() { 579 return super.toString(); 580 } 581 } 582