CallCardFragment.java revision 391bafb1527260cb67ca50327e7443c5d86fed6d
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 17package com.android.incallui; 18 19import android.content.Context; 20import android.graphics.drawable.Drawable; 21import android.os.Bundle; 22import android.telephony.DisconnectCause; 23import android.text.TextUtils; 24import android.view.Gravity; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.View.OnClickListener; 28import android.view.ViewGroup; 29import android.view.ViewStub; 30import android.view.accessibility.AccessibilityEvent; 31import android.widget.Button; 32import android.widget.ImageView; 33import android.widget.TextView; 34 35import com.android.contacts.common.util.ViewUtil; 36 37import java.util.List; 38 39/** 40 * Fragment for call card. 41 */ 42public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi> 43 implements CallCardPresenter.CallCardUi { 44 45 // Primary caller info 46 private TextView mPhoneNumber; 47 private TextView mNumberLabel; 48 private TextView mPrimaryName; 49 private TextView mCallStateLabel; 50 private TextView mCallTypeLabel; 51 private ImageView mPhoto; 52 private TextView mElapsedTime; 53 54 // Secondary caller info 55 private ViewStub mSecondaryCallInfo; 56 private TextView mSecondaryCallName; 57 private ImageView mSecondaryPhoto; 58 private View mSecondaryPhotoOverlay; 59 60 private View mEndCallButton; 61 62 // Cached DisplayMetrics density. 63 private float mDensity; 64 65 @Override 66 CallCardPresenter.CallCardUi getUi() { 67 return this; 68 } 69 70 @Override 71 CallCardPresenter createPresenter() { 72 return new CallCardPresenter(); 73 } 74 75 @Override 76 public void onCreate(Bundle savedInstanceState) { 77 super.onCreate(savedInstanceState); 78 } 79 80 81 @Override 82 public void onActivityCreated(Bundle savedInstanceState) { 83 super.onActivityCreated(savedInstanceState); 84 85 final CallList calls = CallList.getInstance(); 86 final Call call = calls.getFirstCall(); 87 getPresenter().init(getActivity(), call); 88 } 89 90 @Override 91 public View onCreateView(LayoutInflater inflater, ViewGroup container, 92 Bundle savedInstanceState) { 93 super.onCreateView(inflater, container, savedInstanceState); 94 95 mDensity = getResources().getDisplayMetrics().density; 96 97 return inflater.inflate(R.layout.call_card, container, false); 98 } 99 100 @Override 101 public void onViewCreated(View view, Bundle savedInstanceState) { 102 super.onViewCreated(view, savedInstanceState); 103 104 mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber); 105 mPrimaryName = (TextView) view.findViewById(R.id.name); 106 mNumberLabel = (TextView) view.findViewById(R.id.label); 107 mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info); 108 mPhoto = (ImageView) view.findViewById(R.id.photo); 109 mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel); 110 mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel); 111 mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime); 112 113 mEndCallButton = view.findViewById(R.id.endButton); 114 mEndCallButton.setOnClickListener(new View.OnClickListener() { 115 @Override 116 public void onClick(View v) { 117 getPresenter().endCallClicked(); 118 } 119 }); 120 ViewUtil.setupFloatingActionButton(mEndCallButton, getResources()); 121 } 122 123 @Override 124 public void setVisible(boolean on) { 125 if (on) { 126 getView().setVisibility(View.VISIBLE); 127 } else { 128 getView().setVisibility(View.INVISIBLE); 129 } 130 } 131 132 public void setShowConnectionHandoff(boolean showConnectionHandoff) { 133 Log.v(this, "setShowConnectionHandoff: " + showConnectionHandoff); 134 } 135 136 @Override 137 public void setPrimaryName(String name, boolean nameIsNumber) { 138 if (TextUtils.isEmpty(name)) { 139 mPrimaryName.setText(""); 140 } else { 141 mPrimaryName.setText(name); 142 143 // Set direction of the name field 144 int nameDirection = View.TEXT_DIRECTION_INHERIT; 145 if (nameIsNumber) { 146 nameDirection = View.TEXT_DIRECTION_LTR; 147 } 148 mPrimaryName.setTextDirection(nameDirection); 149 } 150 } 151 152 @Override 153 public void setPrimaryImage(Drawable image) { 154 if (image != null) { 155 setDrawableToImageView(mPhoto, image); 156 } 157 } 158 159 @Override 160 public void setPrimaryPhoneNumber(String number) { 161 // Set the number 162 if (TextUtils.isEmpty(number)) { 163 mPhoneNumber.setText(""); 164 mPhoneNumber.setVisibility(View.GONE); 165 } else { 166 mPhoneNumber.setText(number); 167 mPhoneNumber.setVisibility(View.VISIBLE); 168 mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR); 169 } 170 } 171 172 @Override 173 public void setPrimaryLabel(String label) { 174 if (!TextUtils.isEmpty(label)) { 175 mNumberLabel.setText(label); 176 mNumberLabel.setVisibility(View.VISIBLE); 177 } else { 178 mNumberLabel.setVisibility(View.GONE); 179 } 180 181 } 182 183 @Override 184 public void setPrimary(String number, String name, boolean nameIsNumber, String label, 185 Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall) { 186 Log.d(this, "Setting primary call"); 187 188 if (isConference) { 189 name = getConferenceString(isGeneric); 190 photo = getConferencePhoto(isGeneric); 191 nameIsNumber = false; 192 } 193 194 setPrimaryPhoneNumber(number); 195 196 // set the name field. 197 setPrimaryName(name, nameIsNumber); 198 199 // Set the label (Mobile, Work, etc) 200 setPrimaryLabel(label); 201 202 showInternetCallLabel(isSipCall); 203 204 setDrawableToImageView(mPhoto, photo); 205 } 206 207 @Override 208 public void setSecondary(boolean show, String name, boolean nameIsNumber, String label, 209 Drawable photo, boolean isConference, boolean isGeneric) { 210 211 if (show) { 212 if (isConference) { 213 name = getConferenceString(isGeneric); 214 photo = getConferencePhoto(isGeneric); 215 nameIsNumber = false; 216 } 217 218 showAndInitializeSecondaryCallInfo(); 219 mSecondaryCallName.setText(name); 220 221 int nameDirection = View.TEXT_DIRECTION_INHERIT; 222 if (nameIsNumber) { 223 nameDirection = View.TEXT_DIRECTION_LTR; 224 } 225 mSecondaryCallName.setTextDirection(nameDirection); 226 227 setDrawableToImageView(mSecondaryPhoto, photo); 228 } else { 229 mSecondaryCallInfo.setVisibility(View.GONE); 230 } 231 } 232 233 @Override 234 public void setSecondaryImage(Drawable image) { 235 if (image != null) { 236 setDrawableToImageView(mSecondaryPhoto, image); 237 } 238 } 239 240 @Override 241 public void setCallState(int state, int cause, boolean bluetoothOn, 242 String gatewayLabel, String gatewayNumber, String wifiConnection) { 243 String callStateLabel = null; 244 245 if (Call.State.isDialing(state) && !TextUtils.isEmpty(gatewayLabel)) { 246 // Provider info: (e.g. "Calling via <gatewayLabel>") 247 callStateLabel = gatewayLabel; 248 } else { 249 callStateLabel = getCallStateLabelFromState(state, cause); 250 } 251 252 Log.v(this, "setCallState " + callStateLabel); 253 Log.v(this, "DisconnectCause " + DisconnectCause.toString(cause)); 254 Log.v(this, "bluetooth on " + bluetoothOn); 255 Log.v(this, "gateway " + gatewayLabel + gatewayNumber); 256 257 // Update the call state label. 258 if (!TextUtils.isEmpty(callStateLabel)) { 259 mCallStateLabel.setText(callStateLabel); 260 mCallStateLabel.setVisibility(View.VISIBLE); 261 } else { 262 mCallStateLabel.setVisibility(View.GONE); 263 } 264 265 if (Call.State.INCOMING == state) { 266 setBluetoothOn(bluetoothOn); 267 } 268 } 269 270 private void showInternetCallLabel(boolean show) { 271 if (show) { 272 final String label = getView().getContext().getString( 273 R.string.incall_call_type_label_sip); 274 mCallTypeLabel.setVisibility(View.VISIBLE); 275 mCallTypeLabel.setText(label); 276 } else { 277 mCallTypeLabel.setVisibility(View.GONE); 278 } 279 } 280 281 @Override 282 public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { 283 if (show) { 284 if (mElapsedTime.getVisibility() != View.VISIBLE) { 285 AnimationUtils.Fade.show(mElapsedTime); 286 } 287 mElapsedTime.setText(callTimeElapsed); 288 } else { 289 // hide() animation has no effect if it is already hidden. 290 AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE); 291 } 292 } 293 294 private void setDrawableToImageView(ImageView view, Drawable photo) { 295 if (photo == null) { 296 photo = view.getResources().getDrawable(R.drawable.picture_unknown); 297 } 298 299 final Drawable current = view.getDrawable(); 300 if (current == null) { 301 view.setImageDrawable(photo); 302 AnimationUtils.Fade.show(view); 303 } else { 304 AnimationUtils.startCrossFade(view, current, photo); 305 view.setVisibility(View.VISIBLE); 306 } 307 } 308 309 private String getConferenceString(boolean isGeneric) { 310 Log.v(this, "isGenericString: " + isGeneric); 311 final int resId = isGeneric ? R.string.card_title_in_call : R.string.card_title_conf_call; 312 return getView().getResources().getString(resId); 313 } 314 315 private Drawable getConferencePhoto(boolean isGeneric) { 316 Log.v(this, "isGenericPhoto: " + isGeneric); 317 final int resId = isGeneric ? R.drawable.picture_dialing : R.drawable.picture_conference; 318 return getView().getResources().getDrawable(resId); 319 } 320 321 private void setBluetoothOn(boolean onOff) { 322 // Also, display a special icon (alongside the "Incoming call" 323 // label) if there's an incoming call and audio will be routed 324 // to bluetooth when you answer it. 325 final int bluetoothIconId = R.drawable.ic_in_call_bt_dk; 326 327 if (onOff) { 328 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0); 329 mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5)); 330 } else { 331 // Clear out any icons 332 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 333 } 334 } 335 336 /** 337 * Gets the call state label based on the state of the call and 338 * cause of disconnect 339 */ 340 private String getCallStateLabelFromState(int state, int cause) { 341 final Context context = getView().getContext(); 342 String callStateLabel = null; // Label to display as part of the call banner 343 344 if (Call.State.IDLE == state) { 345 // "Call state" is meaningless in this state. 346 347 } else if (Call.State.ACTIVE == state) { 348 // We normally don't show a "call state label" at all in 349 // this state (but see below for some special cases). 350 351 } else if (Call.State.ONHOLD == state) { 352 callStateLabel = context.getString(R.string.card_title_on_hold); 353 } else if (Call.State.DIALING == state) { 354 callStateLabel = context.getString(R.string.card_title_dialing); 355 } else if (Call.State.REDIALING == state) { 356 callStateLabel = context.getString(R.string.card_title_redialing); 357 } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) { 358 callStateLabel = context.getString(R.string.card_title_incoming_call); 359 360 } else if (Call.State.DISCONNECTING == state) { 361 // While in the DISCONNECTING state we display a "Hanging up" 362 // message in order to make the UI feel more responsive. (In 363 // GSM it's normal to see a delay of a couple of seconds while 364 // negotiating the disconnect with the network, so the "Hanging 365 // up" state at least lets the user know that we're doing 366 // something. This state is currently not used with CDMA.) 367 callStateLabel = context.getString(R.string.card_title_hanging_up); 368 369 } else if (Call.State.DISCONNECTED == state) { 370 callStateLabel = getCallFailedString(cause); 371 372 } else { 373 Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state); 374 } 375 376 return callStateLabel; 377 } 378 379 /** 380 * Maps the disconnect cause to a resource string. 381 * 382 * @param cause disconnect cause as defined in {@link DisconnectCause} 383 */ 384 private String getCallFailedString(int cause) { 385 int resID = R.string.card_title_call_ended; 386 387 // TODO: The card *title* should probably be "Call ended" in all 388 // cases, but if the DisconnectCause was an error condition we should 389 // probably also display the specific failure reason somewhere... 390 391 switch (cause) { 392 case DisconnectCause.BUSY: 393 resID = R.string.callFailed_userBusy; 394 break; 395 396 case DisconnectCause.CONGESTION: 397 resID = R.string.callFailed_congestion; 398 break; 399 400 case DisconnectCause.TIMED_OUT: 401 resID = R.string.callFailed_timedOut; 402 break; 403 404 case DisconnectCause.SERVER_UNREACHABLE: 405 resID = R.string.callFailed_server_unreachable; 406 break; 407 408 case DisconnectCause.NUMBER_UNREACHABLE: 409 resID = R.string.callFailed_number_unreachable; 410 break; 411 412 case DisconnectCause.INVALID_CREDENTIALS: 413 resID = R.string.callFailed_invalid_credentials; 414 break; 415 416 case DisconnectCause.SERVER_ERROR: 417 resID = R.string.callFailed_server_error; 418 break; 419 420 case DisconnectCause.OUT_OF_NETWORK: 421 resID = R.string.callFailed_out_of_network; 422 break; 423 424 case DisconnectCause.LOST_SIGNAL: 425 case DisconnectCause.CDMA_DROP: 426 resID = R.string.callFailed_noSignal; 427 break; 428 429 case DisconnectCause.LIMIT_EXCEEDED: 430 resID = R.string.callFailed_limitExceeded; 431 break; 432 433 case DisconnectCause.POWER_OFF: 434 resID = R.string.callFailed_powerOff; 435 break; 436 437 case DisconnectCause.ICC_ERROR: 438 resID = R.string.callFailed_simError; 439 break; 440 441 case DisconnectCause.OUT_OF_SERVICE: 442 resID = R.string.callFailed_outOfService; 443 break; 444 445 case DisconnectCause.INVALID_NUMBER: 446 case DisconnectCause.UNOBTAINABLE_NUMBER: 447 resID = R.string.callFailed_unobtainable_number; 448 break; 449 450 default: 451 resID = R.string.card_title_call_ended; 452 break; 453 } 454 return this.getView().getContext().getString(resID); 455 } 456 457 private void showAndInitializeSecondaryCallInfo() { 458 mSecondaryCallInfo.setVisibility(View.VISIBLE); 459 460 // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible 461 // until mSecondaryCallInfo is inflated in the call above. 462 if (mSecondaryCallName == null) { 463 mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); 464 } 465 if (mSecondaryPhoto == null) { 466 mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto); 467 } 468 469 if (mSecondaryPhotoOverlay == null) { 470 mSecondaryPhotoOverlay = getView().findViewById(R.id.dim_effect_for_secondary_photo); 471 mSecondaryPhotoOverlay.setOnClickListener(new OnClickListener() { 472 @Override 473 public void onClick(View v) { 474 getPresenter().secondaryPhotoClicked(); 475 } 476 }); 477 mSecondaryPhotoOverlay.setOnTouchListener(new SmallerHitTargetTouchListener()); 478 } 479 } 480 481 public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 482 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 483 dispatchPopulateAccessibilityEvent(event, mPrimaryName); 484 dispatchPopulateAccessibilityEvent(event, mPhoneNumber); 485 return; 486 } 487 dispatchPopulateAccessibilityEvent(event, mCallStateLabel); 488 dispatchPopulateAccessibilityEvent(event, mPrimaryName); 489 dispatchPopulateAccessibilityEvent(event, mPhoneNumber); 490 dispatchPopulateAccessibilityEvent(event, mCallTypeLabel); 491 dispatchPopulateAccessibilityEvent(event, mSecondaryCallName); 492 493 return; 494 } 495 496 public void setEndCallButtonEnabled(boolean enabled) { 497 mEndCallButton.setVisibility(enabled ? View.VISIBLE : View.GONE); 498 } 499 500 private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) { 501 if (view == null) return; 502 final List<CharSequence> eventText = event.getText(); 503 int size = eventText.size(); 504 view.dispatchPopulateAccessibilityEvent(event); 505 // if no text added write null to keep relative position 506 if (size == eventText.size()) { 507 eventText.add(null); 508 } 509 } 510} 511