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