CallCardFragment.java revision 0f09a02be361d5bb36fac6981649204f5d6987fa
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 setPrimaryGateway(String gatewayLabel, String gatewayNumber) { 171 if (!TextUtils.isEmpty(gatewayLabel) && !TextUtils.isEmpty(gatewayNumber)) { 172 mProviderLabel.setText(gatewayLabel); 173 mProviderNumber.setText(gatewayNumber); 174 mProviderInfo.setVisibility(View.VISIBLE); 175 } else { 176 mProviderInfo.setVisibility(View.GONE); 177 } 178 } 179 180 @Override 181 public void setPrimary(String number, String name, boolean nameIsNumber, String label, 182 Drawable photo, boolean isConference, String gatewayLabel, String gatewayNumber) { 183 Log.d(this, "Setting primary call [" + gatewayLabel + "][" + gatewayNumber + "]"); 184 185 if (isConference) { 186 name = getView().getResources().getString(R.string.card_title_conf_call); 187 photo = getView().getResources().getDrawable(R.drawable.picture_conference); 188 nameIsNumber = false; 189 } 190 191 setPrimaryPhoneNumber(number); 192 193 // Set any gateway information 194 setPrimaryGateway(gatewayLabel, gatewayNumber); 195 196 // set the name field. 197 setPrimaryName(name, nameIsNumber); 198 199 // Set the label (Mobile, Work, etc) 200 setPrimaryLabel(label); 201 202 setDrawableToImageView(mPhoto, photo); 203 } 204 205 @Override 206 public void setSecondary(boolean show, String name, boolean nameIsNumber, String label, 207 Drawable photo, boolean isConference) { 208 209 if (show) { 210 if (isConference) { 211 name = getView().getResources().getString(R.string.card_title_conf_call); 212 photo = getView().getResources().getDrawable(R.drawable.picture_conference); 213 nameIsNumber = false; 214 } 215 216 showAndInitializeSecondaryCallInfo(); 217 mSecondaryCallName.setText(name); 218 219 int nameDirection = View.TEXT_DIRECTION_INHERIT; 220 if (nameIsNumber) { 221 nameDirection = View.TEXT_DIRECTION_LTR; 222 } 223 mSecondaryCallName.setTextDirection(nameDirection); 224 225 setDrawableToImageView(mSecondaryPhoto, photo); 226 } else { 227 mSecondaryCallInfo.setVisibility(View.GONE); 228 } 229 } 230 231 @Override 232 public void setSecondaryImage(Bitmap bitmap) { 233 if (bitmap != null) { 234 setDrawableToImageView(mSecondaryPhoto, new BitmapDrawable(getResources(), bitmap)); 235 } 236 } 237 238 @Override 239 public void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn) { 240 String callStateLabel = null; 241 242 // States other than disconnected not yet supported 243 callStateLabel = getCallStateLabelFromState(state, cause); 244 245 Log.v(this, "setCallState ", callStateLabel); 246 Log.v(this, "DisconnectCause ", cause); 247 Log.v(this, "bluetooth on ", bluetoothOn); 248 249 if (!TextUtils.isEmpty(callStateLabel)) { 250 // There are cases where we totally skip the animation 251 final boolean skipAnimation = (state == Call.State.DIALING 252 || state == Call.State.DISCONNECTED); 253 254 LayoutTransition transition = null; 255 if (skipAnimation) { 256 transition = mSupplementaryInfoContainer.getLayoutTransition(); 257 mSupplementaryInfoContainer.setLayoutTransition(null); 258 } 259 260 mCallStateLabel.setVisibility(View.VISIBLE); 261 mCallStateLabel.setText(callStateLabel); 262 263 if (skipAnimation) { 264 mSupplementaryInfoContainer.setLayoutTransition(transition); 265 } 266 267 if (Call.State.INCOMING == state) { 268 setBluetoothOn(bluetoothOn); 269 } 270 } else { 271 mCallStateLabel.setVisibility(View.GONE); 272 // Gravity is aligned left when receiving an incoming call in landscape. 273 // In that rare case, the gravity needs to be reset to the right. 274 // Also, setText("") is used since there is a delay in making the view GONE, 275 // so the user will otherwise see the text jump to the right side before disappearing. 276 if(mCallStateLabel.getGravity() != Gravity.END) { 277 mCallStateLabel.setText(""); 278 mCallStateLabel.setGravity(Gravity.END); 279 } 280 } 281 } 282 283 @Override 284 public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { 285 if (show) { 286 if (mElapsedTime.getVisibility() != View.VISIBLE) { 287 AnimationUtils.Fade.show(mElapsedTime); 288 } 289 mElapsedTime.setText(callTimeElapsed); 290 } else { 291 // hide() animation has no effect if it is already hidden. 292 AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE); 293 } 294 } 295 296 private void setDrawableToImageView(ImageView view, Drawable photo) { 297 if (photo == null) { 298 photo = view.getResources().getDrawable(R.drawable.picture_unknown); 299 } 300 301 final Drawable current = view.getDrawable(); 302 if (current == null) { 303 view.setImageDrawable(photo); 304 AnimationUtils.Fade.show(view); 305 } else { 306 AnimationUtils.startCrossFade(view, current, photo); 307 mPhoto.setVisibility(View.VISIBLE); 308 } 309 } 310 311 private void setBluetoothOn(boolean onOff) { 312 // Also, display a special icon (alongside the "Incoming call" 313 // label) if there's an incoming call and audio will be routed 314 // to bluetooth when you answer it. 315 final int bluetoothIconId = R.drawable.ic_incoming_call_bluetooth; 316 317 if (onOff) { 318 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0); 319 mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5)); 320 } else { 321 // Clear out any icons 322 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 323 } 324 } 325 326 /** 327 * Gets the call state label based on the state of the call and 328 * cause of disconnect 329 */ 330 private String getCallStateLabelFromState(int state, Call.DisconnectCause cause) { 331 final Context context = getView().getContext(); 332 String callStateLabel = null; // Label to display as part of the call banner 333 334 if (Call.State.IDLE == state) { 335 // "Call state" is meaningless in this state. 336 337 } else if (Call.State.ACTIVE == state) { 338 // We normally don't show a "call state label" at all in 339 // this state (but see below for some special cases). 340 341 } else if (Call.State.ONHOLD == state) { 342 callStateLabel = context.getString(R.string.card_title_on_hold); 343 344 } else if (Call.State.DIALING == state) { 345 callStateLabel = context.getString(R.string.card_title_dialing); 346 347 } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) { 348 callStateLabel = context.getString(R.string.card_title_incoming_call); 349 350 // TODO(klp): Add a disconnecting state 351 //} else if (Call.State.DISCONNECTING) { 352 // While in the DISCONNECTING state we display a "Hanging up" 353 // message in order to make the UI feel more responsive. (In 354 // GSM it's normal to see a delay of a couple of seconds while 355 // negotiating the disconnect with the network, so the "Hanging 356 // up" state at least lets the user know that we're doing 357 // something. This state is currently not used with CDMA.) 358 //callStateLabel = context.getString(R.string.card_title_hanging_up); 359 360 } else if (Call.State.DISCONNECTED == state) { 361 callStateLabel = getCallFailedString(cause); 362 363 } else { 364 Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state); 365 } 366 367 return callStateLabel; 368 } 369 370 /** 371 * Maps the disconnect cause to a resource string. 372 */ 373 private String getCallFailedString(Call.DisconnectCause cause) { 374 int resID = R.string.card_title_call_ended; 375 376 // TODO: The card *title* should probably be "Call ended" in all 377 // cases, but if the DisconnectCause was an error condition we should 378 // probably also display the specific failure reason somewhere... 379 380 switch (cause) { 381 case BUSY: 382 resID = R.string.callFailed_userBusy; 383 break; 384 385 case CONGESTION: 386 resID = R.string.callFailed_congestion; 387 break; 388 389 case TIMED_OUT: 390 resID = R.string.callFailed_timedOut; 391 break; 392 393 case SERVER_UNREACHABLE: 394 resID = R.string.callFailed_server_unreachable; 395 break; 396 397 case NUMBER_UNREACHABLE: 398 resID = R.string.callFailed_number_unreachable; 399 break; 400 401 case INVALID_CREDENTIALS: 402 resID = R.string.callFailed_invalid_credentials; 403 break; 404 405 case SERVER_ERROR: 406 resID = R.string.callFailed_server_error; 407 break; 408 409 case OUT_OF_NETWORK: 410 resID = R.string.callFailed_out_of_network; 411 break; 412 413 case LOST_SIGNAL: 414 case CDMA_DROP: 415 resID = R.string.callFailed_noSignal; 416 break; 417 418 case LIMIT_EXCEEDED: 419 resID = R.string.callFailed_limitExceeded; 420 break; 421 422 case POWER_OFF: 423 resID = R.string.callFailed_powerOff; 424 break; 425 426 case ICC_ERROR: 427 resID = R.string.callFailed_simError; 428 break; 429 430 case OUT_OF_SERVICE: 431 resID = R.string.callFailed_outOfService; 432 break; 433 434 case INVALID_NUMBER: 435 case UNOBTAINABLE_NUMBER: 436 resID = R.string.callFailed_unobtainable_number; 437 break; 438 439 default: 440 resID = R.string.card_title_call_ended; 441 break; 442 } 443 return this.getView().getContext().getString(resID); 444 } 445 446 private void showAndInitializeSecondaryCallInfo() { 447 mSecondaryCallInfo.setVisibility(View.VISIBLE); 448 449 // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible 450 // until mSecondaryCallInfo is inflated in the call above. 451 if (mSecondaryCallName == null) { 452 mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); 453 } 454 if (mSecondaryPhoto == null) { 455 mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto); 456 } 457 458 if (mSecondaryPhotoOverlay == null) { 459 mSecondaryPhotoOverlay = getView().findViewById(R.id.dim_effect_for_secondary_photo); 460 mSecondaryPhotoOverlay.setOnClickListener(new OnClickListener() { 461 @Override 462 public void onClick(View v) { 463 getPresenter().secondaryPhotoClicked(); 464 } 465 }); 466 } 467 } 468} 469