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