CallCardFragment.java revision 88dbc6b3dbf35b322fe09c1a289c7e884e8fba22
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.app.Activity; 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.ViewGroup; 30import android.view.ViewStub; 31import android.widget.ImageView; 32import android.widget.TextView; 33 34import com.android.services.telephony.common.Call; 35 36/** 37 * Fragment for call card. 38 */ 39public class CallCardFragment extends BaseFragment<CallCardPresenter> 40 implements CallCardPresenter.CallCardUi { 41 42 // Primary caller info 43 private TextView mPhoneNumber; 44 private TextView mNumberLabel; 45 private TextView mName; 46 private TextView mCallStateLabel; 47 private ImageView mPhoto; 48 private TextView mElapsedTime; 49 50 // Secondary caller info 51 private ViewStub mSecondaryCallInfo; 52 private TextView mSecondaryCallName; 53 private ImageView mSecondaryPhoto; 54 55 // Cached DisplayMetrics density. 56 private float mDensity; 57 58 @Override 59 CallCardPresenter createPresenter() { 60 return new CallCardPresenter(); 61 } 62 63 @Override 64 public View onCreateView(LayoutInflater inflater, ViewGroup container, 65 Bundle savedInstanceState) { 66 mDensity = getResources().getDisplayMetrics().density; 67 68 return inflater.inflate(R.layout.call_card, container, false); 69 } 70 71 72 @Override 73 public void onViewCreated(View view, Bundle savedInstanceState) { 74 mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber); 75 mName = (TextView) view.findViewById(R.id.name); 76 mNumberLabel = (TextView) view.findViewById(R.id.label); 77 mSecondaryCallInfo = (ViewStub) view.findViewById(R.id.secondary_call_info); 78 mPhoto = (ImageView) view.findViewById(R.id.photo); 79 mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel); 80 mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime); 81 82 // This method call will begin the callbacks on CallCardUi. We need to ensure 83 // everything needed for the callbacks is set up before this is called. 84 getPresenter().onUiReady(this); 85 } 86 87 @Override 88 public void onDestroyView() { 89 super.onDestroyView(); 90 getPresenter().onUiUnready(this); 91 } 92 93 @Override 94 public void setVisible(boolean on) { 95 if (on) { 96 getView().setVisibility(View.VISIBLE); 97 } else { 98 getView().setVisibility(View.INVISIBLE); 99 } 100 } 101 102 @Override 103 public void setPrimary(String number, String name, String label, Drawable photo) { 104 boolean nameIsNumber = false; 105 106 // If there is no name, then use the number as the name; 107 if (TextUtils.isEmpty(name)) { 108 name = number; 109 number = null; 110 nameIsNumber = true; 111 } 112 113 // Set the number 114 if (TextUtils.isEmpty(number)) { 115 mPhoneNumber.setText(""); 116 mPhoneNumber.setVisibility(View.GONE); 117 } else { 118 mPhoneNumber.setText(number); 119 mPhoneNumber.setVisibility(View.VISIBLE); 120 mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR); 121 } 122 123 // Set direction of the name field 124 125 // set the name field. 126 if (TextUtils.isEmpty(name)) { 127 mName.setText(""); 128 } else { 129 mName.setText(name); 130 131 int nameDirection = View.TEXT_DIRECTION_INHERIT; 132 if (nameIsNumber) { 133 nameDirection = View.TEXT_DIRECTION_LTR; 134 } 135 mName.setTextDirection(nameDirection); 136 } 137 138 // Set the label (Mobile, Work, etc) 139 if (!TextUtils.isEmpty(label)) { 140 mNumberLabel.setText(label); 141 mNumberLabel.setVisibility(View.VISIBLE); 142 } else { 143 mNumberLabel.setVisibility(View.GONE); 144 } 145 146 setDrawableToImageView(mPhoto, photo); 147 } 148 149 @Override 150 public void setSecondary(boolean show, String number, String name, String label, 151 Drawable photo) { 152 153 if (show) { 154 showAndInitializeSecondaryCallInfo(); 155 if (TextUtils.isEmpty(name)) { 156 name = number; 157 } 158 159 mSecondaryCallName.setText(name); 160 setDrawableToImageView(mSecondaryPhoto, photo); 161 } else { 162 mSecondaryCallInfo.setVisibility(View.GONE); 163 } 164 } 165 166 @Override 167 public void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn) { 168 String callStateLabel = null; 169 170 // States other than disconnected not yet supported 171 callStateLabel = getCallStateLabelFromState(state, cause); 172 173 Logger.v(this, "setCallState ", callStateLabel); 174 Logger.v(this, "DisconnectCause ", cause); 175 Logger.v(this, "bluetooth on ", bluetoothOn); 176 177 if (!TextUtils.isEmpty(callStateLabel)) { 178 mCallStateLabel.setVisibility(View.VISIBLE); 179 mCallStateLabel.setText(callStateLabel); 180 181 if (Call.State.INCOMING == state) { 182 setBluetoothOn(bluetoothOn); 183 } 184 } else { 185 mCallStateLabel.setVisibility(View.GONE); 186 // Gravity is aligned left when receiving an incoming call in landscape. 187 // In that rare case, the gravity needs to be reset to the right. 188 // Also, setText("") is used since there is a delay in making the view GONE, 189 // so the user will otherwise see the text jump to the right side before disappearing. 190 if(mCallStateLabel.getGravity() != Gravity.END) { 191 mCallStateLabel.setText(""); 192 mCallStateLabel.setGravity(Gravity.END); 193 } 194 } 195 } 196 197 @Override 198 public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { 199 if (show) { 200 if (mElapsedTime.getVisibility() != View.VISIBLE) { 201 AnimationUtils.Fade.show(mElapsedTime); 202 } 203 mElapsedTime.setText(callTimeElapsed); 204 } else { 205 // hide() animation has no effect if it is already hidden. 206 AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE); 207 } 208 } 209 210 private void setDrawableToImageView(ImageView view, Drawable photo) { 211 if (photo == null) { 212 photo = view.getResources().getDrawable(R.drawable.picture_unknown); 213 } 214 215 final Drawable current = view.getDrawable(); 216 if (current == null) { 217 view.setImageDrawable(photo); 218 AnimationUtils.Fade.show(view); 219 } else { 220 AnimationUtils.startCrossFade(view, current, photo); 221 mPhoto.setVisibility(View.VISIBLE); 222 } 223 } 224 225 private void setBluetoothOn(boolean onOff) { 226 // Also, display a special icon (alongside the "Incoming call" 227 // label) if there's an incoming call and audio will be routed 228 // to bluetooth when you answer it. 229 final int bluetoothIconId = R.drawable.ic_incoming_call_bluetooth; 230 231 if (onOff) { 232 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0); 233 mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5)); 234 } else { 235 // Clear out any icons 236 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 237 } 238 } 239 240 /** 241 * Gets the call state label based on the state of the call and 242 * cause of disconnect 243 */ 244 private String getCallStateLabelFromState(int state, Call.DisconnectCause cause) { 245 final Context context = getView().getContext(); 246 String callStateLabel = null; // Label to display as part of the call banner 247 248 if (Call.State.IDLE == state) { 249 // "Call state" is meaningless in this state. 250 251 } else if (Call.State.ACTIVE == state) { 252 // We normally don't show a "call state label" at all in 253 // this state (but see below for some special cases). 254 255 } else if (Call.State.ONHOLD == state) { 256 callStateLabel = context.getString(R.string.card_title_on_hold); 257 258 } else if (Call.State.DIALING == state) { 259 callStateLabel = context.getString(R.string.card_title_dialing); 260 261 } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) { 262 callStateLabel = context.getString(R.string.card_title_incoming_call); 263 264 // TODO(klp): Add a disconnecting state 265 //} else if (Call.State.DISCONNECTING) { 266 // While in the DISCONNECTING state we display a "Hanging up" 267 // message in order to make the UI feel more responsive. (In 268 // GSM it's normal to see a delay of a couple of seconds while 269 // negotiating the disconnect with the network, so the "Hanging 270 // up" state at least lets the user know that we're doing 271 // something. This state is currently not used with CDMA.) 272 //callStateLabel = context.getString(R.string.card_title_hanging_up); 273 274 } else if (Call.State.DISCONNECTED == state) { 275 callStateLabel = getCallFailedString(cause); 276 277 } else { 278 Logger.wtf(this, "updateCallStateWidgets: unexpected call state: " + state); 279 } 280 281 return callStateLabel; 282 } 283 284 /** 285 * Maps the disconnect cause to a resource string. 286 */ 287 private String getCallFailedString(Call.DisconnectCause cause) { 288 int resID = R.string.card_title_call_ended; 289 290 // TODO: The card *title* should probably be "Call ended" in all 291 // cases, but if the DisconnectCause was an error condition we should 292 // probably also display the specific failure reason somewhere... 293 294 switch (cause) { 295 case BUSY: 296 resID = R.string.callFailed_userBusy; 297 break; 298 299 case CONGESTION: 300 resID = R.string.callFailed_congestion; 301 break; 302 303 case TIMED_OUT: 304 resID = R.string.callFailed_timedOut; 305 break; 306 307 case SERVER_UNREACHABLE: 308 resID = R.string.callFailed_server_unreachable; 309 break; 310 311 case NUMBER_UNREACHABLE: 312 resID = R.string.callFailed_number_unreachable; 313 break; 314 315 case INVALID_CREDENTIALS: 316 resID = R.string.callFailed_invalid_credentials; 317 break; 318 319 case SERVER_ERROR: 320 resID = R.string.callFailed_server_error; 321 break; 322 323 case OUT_OF_NETWORK: 324 resID = R.string.callFailed_out_of_network; 325 break; 326 327 case LOST_SIGNAL: 328 case CDMA_DROP: 329 resID = R.string.callFailed_noSignal; 330 break; 331 332 case LIMIT_EXCEEDED: 333 resID = R.string.callFailed_limitExceeded; 334 break; 335 336 case POWER_OFF: 337 resID = R.string.callFailed_powerOff; 338 break; 339 340 case ICC_ERROR: 341 resID = R.string.callFailed_simError; 342 break; 343 344 case OUT_OF_SERVICE: 345 resID = R.string.callFailed_outOfService; 346 break; 347 348 case INVALID_NUMBER: 349 case UNOBTAINABLE_NUMBER: 350 resID = R.string.callFailed_unobtainable_number; 351 break; 352 353 default: 354 resID = R.string.card_title_call_ended; 355 break; 356 } 357 return this.getView().getContext().getString(resID); 358 } 359 360 private void showAndInitializeSecondaryCallInfo() { 361 mSecondaryCallInfo.setVisibility(View.VISIBLE); 362 363 // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible 364 // until mSecondaryCallInfo is inflated in the call above. 365 if (mSecondaryCallName == null) { 366 mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); 367 } 368 if (mSecondaryPhoto == null) { 369 mSecondaryPhoto = (ImageView) getView().findViewById(R.id.secondaryCallPhoto); 370 } 371 } 372} 373